Source Code Cross Referenced for AccountingDocumentRuleBase.java in  » ERP-CRM-Financial » Kuali-Financial-System » org » kuali » kfs » rules » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » ERP CRM Financial » Kuali Financial System » org.kuali.kfs.rules 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * Copyright 2007 The Kuali Foundation.
0003:         * 
0004:         * Licensed under the Educational Community License, Version 1.0 (the "License");
0005:         * you may not use this file except in compliance with the License.
0006:         * You may obtain a copy of the License at
0007:         * 
0008:         * http://www.opensource.org/licenses/ecl1.php
0009:         * 
0010:         * Unless required by applicable law or agreed to in writing, software
0011:         * distributed under the License is distributed on an "AS IS" BASIS,
0012:         * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013:         * See the License for the specific language governing permissions and
0014:         * limitations under the License.
0015:         */
0016:        package org.kuali.kfs.rules;
0017:
0018:        import static org.kuali.kfs.KFSConstants.ACCOUNTING_LINE_ERRORS;
0019:        import static org.kuali.kfs.KFSConstants.AMOUNT_PROPERTY_NAME;
0020:        import static org.kuali.kfs.KFSConstants.BALANCE_TYPE_ACTUAL;
0021:        import static org.kuali.kfs.KFSConstants.BLANK_SPACE;
0022:        import static org.kuali.kfs.KFSConstants.SOURCE_ACCOUNTING_LINE_ERRORS;
0023:        import static org.kuali.kfs.KFSConstants.SOURCE_ACCOUNTING_LINE_ERROR_PATTERN;
0024:        import static org.kuali.kfs.KFSConstants.TARGET_ACCOUNTING_LINE_ERRORS;
0025:        import static org.kuali.kfs.KFSConstants.TARGET_ACCOUNTING_LINE_ERROR_PATTERN;
0026:        import static org.kuali.kfs.KFSConstants.ZERO;
0027:        import static org.kuali.kfs.KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_ADD;
0028:        import static org.kuali.kfs.KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_DELETE;
0029:        import static org.kuali.kfs.KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_UPDATE;
0030:        import static org.kuali.kfs.KFSKeyConstants.ERROR_ACCOUNTINGLINE_LASTACCESSIBLE_DELETE;
0031:        import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_ACCOUNTING_LINE_INVALID_FORMAT;
0032:        import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_ACCOUNTING_LINE_MAX_LENGTH;
0033:        import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_BALANCE;
0034:        import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_FUND_GROUP_SET_DOES_NOT_BALANCE;
0035:        import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_OPTIONAL_ONE_SIDED_DOCUMENT_REQUIRED_NUMBER_OF_ACCOUNTING_LINES_NOT_MET;
0036:        import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_SINGLE_ACCOUNTING_LINE_SECTION_TOTAL_CHANGED;
0037:        import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_SOURCE_SECTION_NO_ACCOUNTING_LINES;
0038:        import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_TARGET_SECTION_NO_ACCOUNTING_LINES;
0039:        import static org.kuali.kfs.KFSKeyConstants.ERROR_INVALID_FORMAT;
0040:        import static org.kuali.kfs.KFSKeyConstants.ERROR_INVALID_NEGATIVE_AMOUNT_NON_CORRECTION;
0041:        import static org.kuali.kfs.KFSKeyConstants.ERROR_MAX_LENGTH;
0042:        import static org.kuali.kfs.KFSKeyConstants.ERROR_ZERO_AMOUNT;
0043:        import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_FUND_GROUP_CODES;
0044:        import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_OBJECT_CODES;
0045:        import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_OBJECT_CONSOLIDATIONS;
0046:        import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_OBJECT_LEVELS;
0047:        import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_OBJECT_SUB_TYPE_CODES;
0048:        import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_OBJECT_TYPE_CODES;
0049:        import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_SUB_FUND_GROUP_CODES;
0050:
0051:        import java.lang.reflect.InvocationTargetException;
0052:        import java.sql.Timestamp;
0053:        import java.util.ArrayList;
0054:        import java.util.Arrays;
0055:        import java.util.Iterator;
0056:        import java.util.List;
0057:        import java.util.ListIterator;
0058:
0059:        import org.apache.commons.beanutils.PropertyUtils;
0060:        import org.apache.commons.lang.StringUtils;
0061:        import org.kuali.core.datadictionary.BusinessObjectEntry;
0062:        import org.kuali.core.document.Document;
0063:        import org.kuali.core.exceptions.ValidationException;
0064:        import org.kuali.core.rule.event.ApproveDocumentEvent;
0065:        import org.kuali.core.rule.event.BlanketApproveDocumentEvent;
0066:        import org.kuali.core.service.DataDictionaryService;
0067:        import org.kuali.core.service.DateTimeService;
0068:        import org.kuali.core.service.DictionaryValidationService;
0069:        import org.kuali.core.service.DocumentService;
0070:        import org.kuali.core.service.DocumentTypeService;
0071:        import org.kuali.core.util.ErrorMessage;
0072:        import org.kuali.core.util.ExceptionUtils;
0073:        import org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper;
0074:        import org.kuali.core.util.GlobalVariables;
0075:        import org.kuali.core.util.KualiDecimal;
0076:        import org.kuali.core.util.ObjectUtils;
0077:        import org.kuali.core.web.format.CurrencyFormatter;
0078:        import org.kuali.core.workflow.service.KualiWorkflowDocument;
0079:        import org.kuali.kfs.KFSConstants;
0080:        import org.kuali.kfs.KFSKeyConstants;
0081:        import org.kuali.kfs.KFSPropertyConstants;
0082:        import org.kuali.kfs.bo.AccountingLine;
0083:        import org.kuali.kfs.bo.GeneralLedgerPendingEntry;
0084:        import org.kuali.kfs.bo.SourceAccountingLine;
0085:        import org.kuali.kfs.context.SpringContext;
0086:        import org.kuali.kfs.document.AccountingDocument;
0087:        import org.kuali.kfs.rule.AddAccountingLineRule;
0088:        import org.kuali.kfs.rule.DeleteAccountingLineRule;
0089:        import org.kuali.kfs.rule.GenerateGeneralLedgerPendingEntriesRule;
0090:        import org.kuali.kfs.rule.ReviewAccountingLineRule;
0091:        import org.kuali.kfs.rule.SufficientFundsCheckingPreparationRule;
0092:        import org.kuali.kfs.rule.UpdateAccountingLineRule;
0093:        import org.kuali.kfs.service.GeneralLedgerPendingEntryService;
0094:        import org.kuali.kfs.service.HomeOriginationService;
0095:        import org.kuali.kfs.service.OptionsService;
0096:        import org.kuali.kfs.service.ParameterEvaluator;
0097:        import org.kuali.kfs.service.ParameterService;
0098:        import org.kuali.kfs.service.impl.ParameterConstants;
0099:        import org.kuali.module.chart.bo.ChartUser;
0100:        import org.kuali.module.chart.bo.ObjectCode;
0101:        import org.kuali.module.gl.service.SufficientFundsService;
0102:
0103:        import edu.iu.uis.eden.exception.WorkflowException;
0104:
0105:        /**
0106:         * This class contains all of the business rules that are common to all of the Financial Transaction Processing documents. Any
0107:         * document specific business rules are contained within the specific child class that extends off of this one.
0108:         */
0109:        public abstract class AccountingDocumentRuleBase extends
0110:                GeneralLedgerPostingDocumentRuleBase implements 
0111:                AddAccountingLineRule<AccountingDocument>,
0112:                GenerateGeneralLedgerPendingEntriesRule<AccountingDocument>,
0113:                DeleteAccountingLineRule<AccountingDocument>,
0114:                UpdateAccountingLineRule<AccountingDocument>,
0115:                ReviewAccountingLineRule<AccountingDocument>,
0116:                SufficientFundsCheckingPreparationRule,
0117:                AccountingDocumentRuleBaseConstants {
0118:            protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
0119:                    .getLogger(AccountingDocumentRuleBase.class);
0120:            private ParameterService parameterService;
0121:
0122:            protected ParameterService getParameterService() {
0123:                if (parameterService == null) {
0124:                    parameterService = SpringContext
0125:                            .getBean(ParameterService.class);
0126:                }
0127:                return parameterService;
0128:            }
0129:
0130:            /**
0131:             * Indicates what is being done to an accounting line. This allows the same method to be used for different actions.
0132:             */
0133:            public enum AccountingLineAction {
0134:                ADD(ERROR_ACCOUNTINGLINE_INACCESSIBLE_ADD), DELETE(
0135:                        ERROR_ACCOUNTINGLINE_INACCESSIBLE_DELETE), UPDATE(
0136:                        ERROR_ACCOUNTINGLINE_INACCESSIBLE_UPDATE);
0137:
0138:                public final String accessibilityErrorKey;
0139:
0140:                AccountingLineAction(String accessabilityErrorKey) {
0141:                    this .accessibilityErrorKey = accessabilityErrorKey;
0142:                }
0143:            }
0144:
0145:            // Inherited Document Specific Business Rules
0146:            /**
0147:             * This method performs common validation for Transactional Document routes. Note the rule framework will handle validating all
0148:             * of the accounting lines and also those checks that would normally be done on a save, automatically for us.
0149:             * 
0150:             * @param document
0151:             * @return boolean True if the document is valid for routing, false otherwise.
0152:             */
0153:            @Override
0154:            protected boolean processCustomRouteDocumentBusinessRules(
0155:                    Document document) {
0156:                LOG
0157:                        .debug("processCustomRouteDocumentBusinessRules(Document) - start");
0158:
0159:                boolean valid = true;
0160:
0161:                AccountingDocument financialDocument = (AccountingDocument) document;
0162:
0163:                // check to make sure the required number of accounting lines were met
0164:                valid &= isAccountingLinesRequiredNumberForRoutingMet(financialDocument);
0165:
0166:                // check balance
0167:                valid &= isDocumentBalanceValid(financialDocument);
0168:
0169:                LOG
0170:                        .debug("processCustomRouteDocumentBusinessRules(Document) - end");
0171:                return valid;
0172:            }
0173:
0174:            /**
0175:             * This method performs common validation for Transactional Document approvals. Note the rule framework will handle validating
0176:             * all of the accounting lines and also those checks that would normally be done on an approval, automatically for us.
0177:             * 
0178:             * @param approveEvent
0179:             * @return boolean True if the document is valid for approval, false otherwise.
0180:             */
0181:            @Override
0182:            protected boolean processCustomApproveDocumentBusinessRules(
0183:                    ApproveDocumentEvent approveEvent) {
0184:                LOG
0185:                        .debug("processCustomApproveDocumentBusinessRules(ApproveDocumentEvent) - start");
0186:
0187:                boolean valid = true;
0188:
0189:                // allow accountingLine totals to change for BlanketApproveDocumentEvents, and only
0190:                // for BlanketApproveDocumentEvents
0191:                if (!(approveEvent instanceof  BlanketApproveDocumentEvent)) {
0192:                    valid &= isAccountingLineTotalsUnchanged((AccountingDocument) approveEvent
0193:                            .getDocument());
0194:                }
0195:
0196:                LOG
0197:                        .debug("processCustomApproveDocumentBusinessRules(ApproveDocumentEvent) - end");
0198:                return valid;
0199:            }
0200:
0201:            // Rule interface specific methods
0202:            /**
0203:             * This method performs common validation for adding of accounting lines. Then calls a custom method for more specific
0204:             * validation.
0205:             * 
0206:             * @see org.kuali.core.rule.AddAccountingLineRule#processAddAccountingLineBusinessRules(org.kuali.core.document.AccountingDocument,
0207:             *      org.kuali.core.bo.AccountingLine)
0208:             */
0209:            public boolean processAddAccountingLineBusinessRules(
0210:                    AccountingDocument financialDocument,
0211:                    AccountingLine accountingLine) {
0212:                LOG
0213:                        .debug("processAddAccountingLineBusinessRules(AccountingDocument, AccountingLine) - start");
0214:
0215:                boolean valid = checkAccountingLine(financialDocument,
0216:                        accountingLine);
0217:                if (valid) {
0218:                    valid &= checkAccountingLineAccountAccessibility(
0219:                            financialDocument, accountingLine,
0220:                            AccountingLineAction.ADD);
0221:                }
0222:                if (valid) {
0223:                    valid &= processCustomAddAccountingLineBusinessRules(
0224:                            financialDocument, accountingLine);
0225:                }
0226:
0227:                LOG
0228:                        .debug("processAddAccountingLineBusinessRules(AccountingDocument, AccountingLine) - end");
0229:                return valid;
0230:            }
0231:
0232:            /**
0233:             * This method should be overridden in the children classes to implement business rules that don't fit into any of the other
0234:             * AddAccountingLineRule interface methods.
0235:             * 
0236:             * @param financialDocument
0237:             * @param accountingLine
0238:             * @return boolean
0239:             */
0240:            protected boolean processCustomAddAccountingLineBusinessRules(
0241:                    AccountingDocument financialDocument,
0242:                    AccountingLine accountingLine) {
0243:                LOG
0244:                        .debug("processCustomAddAccountingLineBusinessRules(AccountingDocument, AccountingLine) - start");
0245:
0246:                LOG
0247:                        .debug("processCustomAddAccountingLineBusinessRules(AccountingDocument, AccountingLine) - end");
0248:                return true;
0249:            }
0250:
0251:            /**
0252:             * This method performs common validation for deleting of accounting lines. Then calls a custom method for more specific
0253:             * validation.
0254:             * 
0255:             * @see org.kuali.core.rule.DeleteAccountingLineRule#processDeleteAccountingLineBusinessRules(org.kuali.core.document.FinancialDocument,
0256:             *      org.kuali.core.bo.AccountingLine, boolean)
0257:             */
0258:            public boolean processDeleteAccountingLineBusinessRules(
0259:                    AccountingDocument financialDocument,
0260:                    AccountingLine accountingLine,
0261:                    boolean lineWasAlreadyDeletedFromDocument) {
0262:                LOG
0263:                        .debug("processDeleteAccountingLineBusinessRules(AccountingDocument, AccountingLine, boolean) - start");
0264:
0265:                return verifyExistenceOfOtherAccessibleAccountingLines(
0266:                        financialDocument, lineWasAlreadyDeletedFromDocument);
0267:            }
0268:
0269:            /**
0270:             * This method should be overridden in the children classes to implement deleteAccountingLine checks that don't fit into any of
0271:             * the other DeleteAccountingLineRule interface methods.
0272:             * 
0273:             * @param financialDocument
0274:             * @param accountingLine
0275:             * @param lineWasAlreadyDeletedFromDocument
0276:             * @return boolean
0277:             */
0278:            protected boolean processCustomDeleteAccountingLineBusinessRules(
0279:                    AccountingDocument financialDocument,
0280:                    AccountingLine accountingLine,
0281:                    boolean lineWasAlreadyDeletedFromDocument) {
0282:                LOG
0283:                        .debug("processCustomDeleteAccountingLineBusinessRules(AccountingDocument, AccountingLine, boolean) - start");
0284:
0285:                LOG
0286:                        .debug("processCustomDeleteAccountingLineBusinessRules(AccountingDocument, AccountingLine, boolean) - end");
0287:                return true;
0288:            }
0289:
0290:            /**
0291:             * This method verifies that other lines exist on the document that this user has access to.
0292:             * 
0293:             * @param financialDocument
0294:             * @param lineWasAlreadyDeletedFromDocument
0295:             * @return boolean
0296:             */
0297:            private boolean verifyExistenceOfOtherAccessibleAccountingLines(
0298:                    AccountingDocument financialDocument,
0299:                    boolean lineWasAlreadyDeletedFromDocument) {
0300:                LOG
0301:                        .debug("verifyExistenceOfOtherAccessibleAccountingLines(AccountingDocument, boolean) - start");
0302:
0303:                // verify that other accountingLines will exist after the deletion which are accessible to this user
0304:                int minimumRemainingAccessibleLines = 1 + (lineWasAlreadyDeletedFromDocument ? 0
0305:                        : 1);
0306:                boolean sufficientLines = hasAccessibleAccountingLines(
0307:                        financialDocument, minimumRemainingAccessibleLines);
0308:                if (!sufficientLines) {
0309:                    GlobalVariables.getErrorMap().putError(
0310:                            ACCOUNTING_LINE_ERRORS,
0311:                            ERROR_ACCOUNTINGLINE_LASTACCESSIBLE_DELETE);
0312:                }
0313:
0314:                LOG
0315:                        .debug("verifyExistenceOfOtherAccessibleAccountingLines(AccountingDocument, boolean) - end");
0316:                return sufficientLines;
0317:            }
0318:
0319:            /**
0320:             * This method performs common validation for update of accounting lines. Then calls a custom method for more specific
0321:             * validation.
0322:             * 
0323:             * @see org.kuali.core.rule.UpdateAccountingLineRule#processUpdateAccountingLineBusinessRules(org.kuali.core.document.FinancialDocument,
0324:             *      org.kuali.core.bo.AccountingLine, org.kuali.core.bo.AccountingLine)
0325:             */
0326:            public boolean processUpdateAccountingLineBusinessRules(
0327:                    AccountingDocument financialDocument,
0328:                    AccountingLine accountingLine,
0329:                    AccountingLine updatedAccountingLine) {
0330:                LOG
0331:                        .debug("processUpdateAccountingLineBusinessRules(AccountingDocument, AccountingLine, AccountingLine) - start");
0332:
0333:                boolean valid = checkAccountingLine(financialDocument,
0334:                        updatedAccountingLine);
0335:                if (valid) {
0336:                    valid &= checkAccountingLineAccountAccessibility(
0337:                            financialDocument, updatedAccountingLine,
0338:                            AccountingLineAction.UPDATE);
0339:                }
0340:                if (valid) {
0341:                    valid &= processCustomUpdateAccountingLineBusinessRules(
0342:                            financialDocument, accountingLine,
0343:                            updatedAccountingLine);
0344:                }
0345:
0346:                LOG
0347:                        .debug("processUpdateAccountingLineBusinessRules(AccountingDocument, AccountingLine, AccountingLine) - end");
0348:                return valid;
0349:            }
0350:
0351:            /**
0352:             * This method should be overridden in the children classes to implement updateAccountingLine checks that don't fit into any of
0353:             * the other UpdateAccountingLineRule interface methods.
0354:             * 
0355:             * @param accountingDocument
0356:             * @param originalAccountingLine
0357:             * @param updatedAccountingLine
0358:             * @return boolean
0359:             */
0360:            protected boolean processCustomUpdateAccountingLineBusinessRules(
0361:                    AccountingDocument accountingDocument,
0362:                    AccountingLine originalAccountingLine,
0363:                    AccountingLine updatedAccountingLine) {
0364:                LOG
0365:                        .debug("processCustomUpdateAccountingLineBusinessRules(AccountingDocument, AccountingLine, AccountingLine) - start");
0366:
0367:                LOG
0368:                        .debug("processCustomUpdateAccountingLineBusinessRules(AccountingDocument, AccountingLine, AccountingLine) - end");
0369:                return true;
0370:            }
0371:
0372:            /**
0373:             * Wrapper around global errorMap.put call, to allow better logging
0374:             * 
0375:             * @param propertyName
0376:             * @param errorKey
0377:             * @param errorParams
0378:             */
0379:            protected void reportError(String propertyName, String errorKey,
0380:                    String... errorParams) {
0381:                LOG.debug("reportError(String, String, String) - start");
0382:
0383:                GlobalVariables.getErrorMap().putError(propertyName, errorKey,
0384:                        errorParams);
0385:                if (LOG.isDebugEnabled()) {
0386:                    LOG.debug("rule failure at "
0387:                            + ExceptionUtils.describeStackLevels(1, 2));
0388:                }
0389:            }
0390:
0391:            /**
0392:             * Adds a global error for a missing required property. This is used for properties, such as reference origin code, which cannot
0393:             * be required by the DataDictionary validation because not all documents require them.
0394:             * 
0395:             * @param boe
0396:             * @param propertyName
0397:             */
0398:            public static void putRequiredPropertyError(
0399:                    BusinessObjectEntry boe, String propertyName) {
0400:                LOG
0401:                        .debug("putRequiredPropertyError(BusinessObjectEntry, String) - start");
0402:
0403:                String label = boe.getAttributeDefinition(propertyName)
0404:                        .getShortLabel();
0405:                GlobalVariables.getErrorMap().putError(propertyName,
0406:                        KFSKeyConstants.ERROR_REQUIRED, label);
0407:
0408:                LOG
0409:                        .debug("putRequiredPropertyError(BusinessObjectEntry, String) - end");
0410:            }
0411:
0412:            /**
0413:             * If the given accountingLine has an account which is inaccessible to the current user, an error message will be put into the
0414:             * global ErrorMap and into the logfile.
0415:             * 
0416:             * @param financialDocument
0417:             * @param accountingLine
0418:             * @param action
0419:             * @return true if the given accountingLine refers to an account which allows it to be added, deleted, or updated
0420:             */
0421:            protected boolean checkAccountingLineAccountAccessibility(
0422:                    AccountingDocument financialDocument,
0423:                    AccountingLine accountingLine, AccountingLineAction action) {
0424:                LOG
0425:                        .debug("checkAccountingLineAccountAccessibility(AccountingDocument, AccountingLine, AccountingLineAction) - start");
0426:
0427:                boolean isAccessible = accountIsAccessible(financialDocument,
0428:                        accountingLine);
0429:
0430:                // report (and log) errors
0431:                if (!isAccessible) {
0432:                    String[] errorParams = new String[] {
0433:                            accountingLine.getAccountNumber(),
0434:                            GlobalVariables.getUserSession().getUniversalUser()
0435:                                    .getPersonUserIdentifier() };
0436:                    GlobalVariables.getErrorMap().putError(
0437:                            KFSPropertyConstants.ACCOUNT_NUMBER,
0438:                            action.accessibilityErrorKey, errorParams);
0439:
0440:                    LOG.info("accountIsAccessible check failed: account "
0441:                            + errorParams[0] + ", user " + errorParams[1]);
0442:                }
0443:
0444:                LOG
0445:                        .debug("checkAccountingLineAccountAccessibility(AccountingDocument, AccountingLine, AccountingLineAction) - end");
0446:                return isAccessible;
0447:            }
0448:
0449:            /**
0450:             * @param financialDocument
0451:             * @param accountingLine
0452:             * @return true if the given accountingLine refers to an account which allows it to be added, deleted, or updated
0453:             */
0454:            protected boolean accountIsAccessible(
0455:                    AccountingDocument financialDocument,
0456:                    AccountingLine accountingLine) {
0457:                LOG
0458:                        .debug("accountIsAccessible(AccountingDocument, AccountingLine) - start");
0459:
0460:                boolean isAccessible = false;
0461:
0462:                KualiWorkflowDocument workflowDocument = financialDocument
0463:                        .getDocumentHeader().getWorkflowDocument();
0464:                ChartUser currentUser = (ChartUser) GlobalVariables
0465:                        .getUserSession().getUniversalUser().getModuleUser(
0466:                                ChartUser.MODULE_ID);
0467:
0468:                if (workflowDocument.stateIsInitiated()
0469:                        || workflowDocument.stateIsSaved()) {
0470:                    isAccessible = true;
0471:                } else {
0472:                    if (workflowDocument.stateIsEnroute()) {
0473:                        String chartCode = accountingLine
0474:                                .getChartOfAccountsCode();
0475:                        String accountNumber = accountingLine
0476:                                .getAccountNumber();
0477:
0478:                        // if a document is enroute, user can only refer to for accounts for which they are responsible
0479:                        isAccessible = currentUser.isResponsibleForAccount(
0480:                                chartCode, accountNumber);
0481:                    } else {
0482:                        if (workflowDocument.stateIsApproved()
0483:                                || workflowDocument.stateIsFinal()
0484:                                || workflowDocument.stateIsDisapproved()) {
0485:                            isAccessible = false;
0486:                        } else {
0487:                            if (workflowDocument.stateIsException()
0488:                                    && currentUser.getUniversalUser()
0489:                                            .isWorkflowExceptionUser()) {
0490:                                isAccessible = true;
0491:                            }
0492:                        }
0493:                    }
0494:                }
0495:
0496:                LOG
0497:                        .debug("accountIsAccessible(AccountingDocument, AccountingLine) - end");
0498:                return isAccessible;
0499:            }
0500:
0501:            /**
0502:             * @param financialDocument
0503:             * @param min
0504:             * @return true if the document has n (or more) accessible accountingLines
0505:             */
0506:            protected boolean hasAccessibleAccountingLines(
0507:                    AccountingDocument financialDocument, int min) {
0508:                LOG
0509:                        .debug("hasAccessibleAccountingLines(AccountingDocument, int) - start");
0510:
0511:                boolean hasLines = false;
0512:
0513:                // only count if the doc is enroute
0514:                KualiWorkflowDocument workflowDocument = financialDocument
0515:                        .getDocumentHeader().getWorkflowDocument();
0516:                ChartUser currentUser = (ChartUser) GlobalVariables
0517:                        .getUserSession().getUniversalUser().getModuleUser(
0518:                                ChartUser.MODULE_ID);
0519:                if (workflowDocument.stateIsEnroute()) {
0520:                    int accessibleLines = 0;
0521:                    for (Iterator i = financialDocument
0522:                            .getSourceAccountingLines().iterator(); (accessibleLines < min)
0523:                            && i.hasNext();) {
0524:                        AccountingLine line = (AccountingLine) i.next();
0525:                        if (accountIsAccessible(financialDocument, line)) {
0526:                            ++accessibleLines;
0527:                        }
0528:                    }
0529:                    for (Iterator i = financialDocument
0530:                            .getTargetAccountingLines().iterator(); (accessibleLines < min)
0531:                            && i.hasNext();) {
0532:                        AccountingLine line = (AccountingLine) i.next();
0533:                        if (accountIsAccessible(financialDocument, line)) {
0534:                            ++accessibleLines;
0535:                        }
0536:                    }
0537:
0538:                    hasLines = (accessibleLines >= min);
0539:                } else {
0540:                    if (workflowDocument.stateIsException()
0541:                            && currentUser.getUniversalUser()
0542:                                    .isWorkflowExceptionUser()) {
0543:                        hasLines = true;
0544:                    } else {
0545:                        if (workflowDocument.stateIsInitiated()
0546:                                || workflowDocument.stateIsSaved()) {
0547:                            hasLines = true;
0548:                        } else {
0549:                            hasLines = false;
0550:                        }
0551:                    }
0552:                }
0553:
0554:                LOG
0555:                        .debug("hasAccessibleAccountingLines(AccountingDocument, int) - end");
0556:                return hasLines;
0557:            }
0558:
0559:            /**
0560:             * This method performs common validation for review of accounting lines. Then calls a custom method for more specific
0561:             * validation.
0562:             * 
0563:             * @see org.kuali.core.rule.ReviewAccountingLineRule#processReviewAccountingLineBusinessRules(org.kuali.core.document.FinancialDocument,
0564:             *      org.kuali.core.bo.AccountingLine)
0565:             */
0566:            public boolean processReviewAccountingLineBusinessRules(
0567:                    AccountingDocument financialDocument,
0568:                    AccountingLine accountingLine) {
0569:                LOG
0570:                        .debug("processReviewAccountingLineBusinessRules(AccountingDocument, AccountingLine) - start");
0571:
0572:                boolean valid = checkAccountingLine(financialDocument,
0573:                        accountingLine);
0574:                if (valid) {
0575:                    valid &= processCustomReviewAccountingLineBusinessRules(
0576:                            financialDocument, accountingLine);
0577:                }
0578:
0579:                LOG
0580:                        .debug("processReviewAccountingLineBusinessRules(AccountingDocument, AccountingLine) - end");
0581:                return valid;
0582:            }
0583:
0584:            /**
0585:             * This method should be overridden in the child classes to implement business rules that don't fit into any of the other
0586:             * ReviewAccountingLineRule interface methods.
0587:             * 
0588:             * @param financialDocument
0589:             * @param accountingLine
0590:             * @return boolean
0591:             */
0592:            protected boolean processCustomReviewAccountingLineBusinessRules(
0593:                    AccountingDocument financialDocument,
0594:                    AccountingLine accountingLine) {
0595:                LOG
0596:                        .debug("processCustomReviewAccountingLineBusinessRules(AccountingDocument, AccountingLine) - start");
0597:
0598:                LOG
0599:                        .debug("processCustomReviewAccountingLineBusinessRules(AccountingDocument, AccountingLine) - end");
0600:                return true;
0601:            }
0602:
0603:            /**
0604:             * This contains business rule checks that are common to all accounting line events for all Transaction Processing Financial
0605:             * eDocs. Note existence, requirement, and format checking are not done in this method, because those checks are handled
0606:             * automatically by the data dictionary validation framework. This method is responsible for call validate methods that check
0607:             * the activity of an instance.
0608:             * 
0609:             * @param accountingLine
0610:             * @param financialDocument
0611:             * @return true if no errors occurred
0612:             */
0613:            private final boolean checkAccountingLine(
0614:                    AccountingDocument financialDocument,
0615:                    AccountingLine accountingLine) {
0616:                LOG.debug("entering processAccountingLine");
0617:
0618:                boolean valid = true;
0619:                int originalErrorCount = GlobalVariables.getErrorMap()
0620:                        .getErrorCount();
0621:
0622:                // now make sure all the necessary business objects are fully populated
0623:                accountingLine.refreshNonUpdateableReferences();
0624:
0625:                // validate required checks in addition to format checks
0626:                SpringContext.getBean(DictionaryValidationService.class)
0627:                        .validateBusinessObject(accountingLine);
0628:
0629:                // check to see if any errors were reported
0630:                int currentErrorCount = GlobalVariables.getErrorMap()
0631:                        .getErrorCount();
0632:                valid &= (currentErrorCount == originalErrorCount);
0633:
0634:                if (!valid) {
0635:                    // logic to replace generic amount error messages
0636:                    // create a list of accounting line attribute keys
0637:                    ArrayList linePatterns = new ArrayList();
0638:                    // source patterns: removing wildcards
0639:                    linePatterns.addAll(Arrays.asList(StringUtils.replace(
0640:                            SOURCE_ACCOUNTING_LINE_ERROR_PATTERN, "*", "")
0641:                            .split(",")));
0642:                    // target patterns: removing wildcards
0643:                    linePatterns.addAll(Arrays.asList(StringUtils.replace(
0644:                            TARGET_ACCOUNTING_LINE_ERROR_PATTERN, "*", "")
0645:                            .split(",")));
0646:
0647:                    // see if any lines have errors
0648:                    for (Iterator i = GlobalVariables.getErrorMap()
0649:                            .getPropertiesWithErrors().iterator(); i.hasNext();) {
0650:                        String property = (String) i.next();
0651:                        // only concerned about amount field errors
0652:                        if (property.endsWith("." + AMOUNT_PROPERTY_NAME)) {
0653:                            // check if the amount field is associated with an accounting line
0654:                            boolean isLineProperty = true;
0655:                            for (Iterator linePatternsIterator = linePatterns
0656:                                    .iterator(); i.hasNext() && !isLineProperty;) {
0657:                                isLineProperty = property
0658:                                        .startsWith((String) linePatternsIterator
0659:                                                .next());
0660:                            }
0661:                            if (isLineProperty) {
0662:                                // find the specific error messages for the property
0663:                                for (ListIterator errors = GlobalVariables
0664:                                        .getErrorMap().getMessages(property)
0665:                                        .listIterator(); errors.hasNext();) {
0666:                                    ErrorMessage error = (ErrorMessage) errors
0667:                                            .next();
0668:                                    String errorKey = null;
0669:                                    String[] params = new String[2];
0670:                                    if (StringUtils.equals(
0671:                                            ERROR_INVALID_FORMAT, error
0672:                                                    .getErrorKey())) {
0673:                                        errorKey = ERROR_DOCUMENT_ACCOUNTING_LINE_INVALID_FORMAT;
0674:                                        params[1] = accountingLine.getAmount()
0675:                                                .toString();
0676:                                    } else {
0677:                                        if (StringUtils.equals(
0678:                                                ERROR_MAX_LENGTH, error
0679:                                                        .getErrorKey())) {
0680:                                            errorKey = ERROR_DOCUMENT_ACCOUNTING_LINE_MAX_LENGTH;
0681:
0682:                                            // String value = ObjectUtils.getPropertyValue(accountingLine,
0683:                                            // KFSConstants.AMOUNT_PROPERTY_NAME)
0684:
0685:                                        }
0686:                                    }
0687:                                    if (errorKey != null) {
0688:
0689:                                        LOG.debug("replacing: " + error);
0690:                                        // now replace error message
0691:                                        error.setErrorKey(errorKey);
0692:                                        // replace parameters
0693:                                        params[0] = SpringContext.getBean(
0694:                                                DataDictionaryService.class)
0695:                                                .getAttributeLabel(
0696:                                                        accountingLine
0697:                                                                .getClass(),
0698:                                                        AMOUNT_PROPERTY_NAME);
0699:                                        error.setMessageParameters(params);
0700:                                        // put back where it came form
0701:                                        errors.set(error);
0702:                                        LOG.debug("with: " + error);
0703:                                    }
0704:                                }
0705:                            }
0706:                        }
0707:                    }
0708:                } else { // continue on with the rest of the validation if the accounting line contains valid values
0709:                    // Check the amount entered
0710:                    valid &= isAmountValid(financialDocument, accountingLine);
0711:
0712:                    // Perform the standard accounting line rule checking - checks activity
0713:                    // of each attribute in addition to existence
0714:                    valid &= AccountingLineRuleUtil.validateAccountingLine(
0715:                            accountingLine, SpringContext
0716:                                    .getBean(DataDictionaryService.class));
0717:
0718:                    if (valid) { // the following checks assume existence, so if the above method failed, we don't want to call these
0719:                        Class documentClass = getAccountingLineDocumentClass(financialDocument);
0720:
0721:                        // Check the object code to see if it's restricted or not
0722:                        valid &= isObjectCodeAllowed(documentClass,
0723:                                accountingLine);
0724:
0725:                        // Check the object code type allowances
0726:                        valid &= isObjectTypeAllowed(documentClass,
0727:                                accountingLine);
0728:
0729:                        // Check the object sub-type code allowances
0730:                        valid &= isObjectSubTypeAllowed(documentClass,
0731:                                accountingLine);
0732:
0733:                        // Check the object level allowances
0734:                        valid &= isObjectLevelAllowed(documentClass,
0735:                                accountingLine);
0736:
0737:                        // Check the object consolidation allowances
0738:                        valid &= isObjectConsolidationAllowed(documentClass,
0739:                                accountingLine);
0740:
0741:                        // Check the sub fund group allowances
0742:                        valid &= isSubFundGroupAllowed(documentClass,
0743:                                accountingLine);
0744:
0745:                        // Check the fund group allowances
0746:                        valid &= isFundGroupAllowed(documentClass,
0747:                                accountingLine);
0748:                    }
0749:                }
0750:
0751:                if (!valid) {
0752:                    LOG
0753:                            .info("business rule checks failed in processAccountingLine in KualiRuleServiceImpl");
0754:                }
0755:
0756:                LOG.debug("leaving processAccountingLine");
0757:
0758:                return valid;
0759:            }
0760:
0761:            /**
0762:             * This method returns the document class associated with this accounting document and is used to find the appropriate parameter
0763:             * rule This can be overridden to return a different class depending on the situation, initially this is used for Year End
0764:             * documents so that they use the same rules as their parent docs
0765:             * 
0766:             * @see org.kuali.module.financial.rules.YearEndGeneralErrorCorrectionDocumentRule#getAccountingLineDocumentClass(AccountingDocument)
0767:             * @param financialDocument
0768:             * @return documentClass associated with this accounting document
0769:             */
0770:            protected Class getAccountingLineDocumentClass(
0771:                    AccountingDocument financialDocument) {
0772:                return financialDocument.getClass();
0773:            }
0774:
0775:            /**
0776:             * Perform business rules common to all transactional documents when generating general ledger pending entries.
0777:             * 
0778:             * @see org.kuali.core.rule.GenerateGeneralLedgerPendingEntriesRule#processGenerateGeneralLedgerPendingEntries(org.kuali.core.document.AccountingDocument,
0779:             *      org.kuali.core.bo.AccountingLine, org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper)
0780:             */
0781:            public boolean processGenerateGeneralLedgerPendingEntries(
0782:                    AccountingDocument accountingDocument,
0783:                    AccountingLine accountingLine,
0784:                    GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
0785:                LOG
0786:                        .debug("processGenerateGeneralLedgerPendingEntries(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper) - start");
0787:
0788:                // handle the explicit entry
0789:                // create a reference to the explicitEntry to be populated, so we can pass to the offset method later
0790:                boolean success = true;
0791:                GeneralLedgerPendingEntry explicitEntry = new GeneralLedgerPendingEntry();
0792:                success &= processExplicitGeneralLedgerPendingEntry(
0793:                        accountingDocument, sequenceHelper, accountingLine,
0794:                        explicitEntry);
0795:
0796:                // increment the sequence counter
0797:                sequenceHelper.increment();
0798:
0799:                // handle the offset entry
0800:                GeneralLedgerPendingEntry offsetEntry = (GeneralLedgerPendingEntry) ObjectUtils
0801:                        .deepCopy(explicitEntry);
0802:                success &= processOffsetGeneralLedgerPendingEntry(
0803:                        accountingDocument, sequenceHelper, accountingLine,
0804:                        explicitEntry, offsetEntry);
0805:
0806:                // handle the situation where the document is an error correction or is corrected
0807:                handleDocumentErrorCorrection(accountingDocument,
0808:                        accountingLine);
0809:
0810:                LOG
0811:                        .debug("processGenerateGeneralLedgerPendingEntries(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper) - end");
0812:                return success;
0813:            }
0814:
0815:            // Transactional Document Specific Rule Implementations
0816:            /**
0817:             * This method processes all necessary information to build an explicit general ledger entry, and then adds that to the
0818:             * document.
0819:             * 
0820:             * @param accountingDocument
0821:             * @param sequenceHelper
0822:             * @param accountingLine
0823:             * @param explicitEntry
0824:             * @return boolean True if the explicit entry generation was successful, false otherwise.
0825:             */
0826:            protected boolean processExplicitGeneralLedgerPendingEntry(
0827:                    AccountingDocument accountingDocument,
0828:                    GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
0829:                    AccountingLine accountingLine,
0830:                    GeneralLedgerPendingEntry explicitEntry) {
0831:                LOG
0832:                        .debug("processExplicitGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry) - start");
0833:
0834:                // populate the explicit entry
0835:                populateExplicitGeneralLedgerPendingEntry(accountingDocument,
0836:                        accountingLine, sequenceHelper, explicitEntry);
0837:
0838:                // hook for children documents to implement document specific GLPE field mappings
0839:                customizeExplicitGeneralLedgerPendingEntry(accountingDocument,
0840:                        accountingLine, explicitEntry);
0841:
0842:                // add the new explicit entry to the document now
0843:                accountingDocument.getGeneralLedgerPendingEntries().add(
0844:                        explicitEntry);
0845:
0846:                LOG
0847:                        .debug("processExplicitGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry) - end");
0848:                return true;
0849:            }
0850:
0851:            /**
0852:             * This method processes an accounting line's information to build an offset entry, and then adds that to the document.
0853:             * 
0854:             * @param accountingDocument
0855:             * @param sequenceHelper
0856:             * @param accountingLine
0857:             * @param explicitEntry
0858:             * @param offsetEntry
0859:             * @return boolean True if the offset generation is successful.
0860:             */
0861:            protected boolean processOffsetGeneralLedgerPendingEntry(
0862:                    AccountingDocument accountingDocument,
0863:                    GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
0864:                    AccountingLine accountingLine,
0865:                    GeneralLedgerPendingEntry explicitEntry,
0866:                    GeneralLedgerPendingEntry offsetEntry) {
0867:                LOG
0868:                        .debug("processOffsetGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - start");
0869:
0870:                boolean success = true;
0871:                // populate the offset entry
0872:                success &= populateOffsetGeneralLedgerPendingEntry(
0873:                        accountingDocument.getPostingYear(), explicitEntry,
0874:                        sequenceHelper, offsetEntry);
0875:
0876:                // hook for children documents to implement document specific field mappings for the GLPE
0877:                success &= customizeOffsetGeneralLedgerPendingEntry(
0878:                        accountingDocument, accountingLine, explicitEntry,
0879:                        offsetEntry);
0880:
0881:                // add the new offset entry to the document now
0882:                accountingDocument.getGeneralLedgerPendingEntries().add(
0883:                        offsetEntry);
0884:
0885:                LOG
0886:                        .debug("processOffsetGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - end");
0887:                return success;
0888:            }
0889:
0890:            /**
0891:             * This method can be overridden to set attributes on the explicit entry in a way specific to a particular document. By default
0892:             * the explicit entry is returned without modification.
0893:             * 
0894:             * @param accountingDocument
0895:             * @param accountingLine
0896:             * @param explicitEntry
0897:             */
0898:            protected void customizeExplicitGeneralLedgerPendingEntry(
0899:                    AccountingDocument accountingDocument,
0900:                    AccountingLine accountingLine,
0901:                    GeneralLedgerPendingEntry explicitEntry) {
0902:            }
0903:
0904:            /**
0905:             * This method can be overridden to set attributes on the offset entry in a way specific to a particular document. By default
0906:             * the offset entry is not modified.
0907:             * 
0908:             * @param accountingDocument
0909:             * @param accountingLine
0910:             * @param explicitEntry
0911:             * @param offsetEntry
0912:             * @return whether the offset generation is successful
0913:             */
0914:            protected boolean customizeOffsetGeneralLedgerPendingEntry(
0915:                    AccountingDocument accountingDocument,
0916:                    AccountingLine accountingLine,
0917:                    GeneralLedgerPendingEntry explicitEntry,
0918:                    GeneralLedgerPendingEntry offsetEntry) {
0919:                LOG
0920:                        .debug("customizeOffsetGeneralLedgerPendingEntry(AccountingDocument, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - start");
0921:
0922:                LOG
0923:                        .debug("customizeOffsetGeneralLedgerPendingEntry(AccountingDocument, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - end");
0924:                return true;
0925:            }
0926:
0927:            /**
0928:             * Checks accounting line totals for approval to make sure that they have not changed.
0929:             * 
0930:             * @param accountingDocument
0931:             * @return boolean True if the number of accounting lines are valid for routing, false otherwise.
0932:             */
0933:            protected boolean isAccountingLineTotalsUnchanged(
0934:                    AccountingDocument accountingDocument) {
0935:                LOG
0936:                        .debug("isAccountingLineTotalsUnchanged(AccountingDocument) - start");
0937:
0938:                AccountingDocument persistedDocument = null;
0939:
0940:                persistedDocument = retrievePersistedDocument(accountingDocument);
0941:
0942:                boolean isUnchanged = true;
0943:                if (persistedDocument == null) {
0944:                    handleNonExistentDocumentWhenApproving(accountingDocument);
0945:                } else {
0946:                    // retrieve the persisted totals
0947:                    KualiDecimal persistedSourceLineTotal = persistedDocument
0948:                            .getSourceTotal();
0949:                    KualiDecimal persistedTargetLineTotal = persistedDocument
0950:                            .getTargetTotal();
0951:
0952:                    // retrieve the updated totals
0953:                    KualiDecimal currentSourceLineTotal = accountingDocument
0954:                            .getSourceTotal();
0955:                    KualiDecimal currentTargetLineTotal = accountingDocument
0956:                            .getTargetTotal();
0957:
0958:                    // make sure that totals have remained unchanged, if not, recognize that, and
0959:                    // generate appropriate error messages
0960:                    if (currentSourceLineTotal
0961:                            .compareTo(persistedSourceLineTotal) != 0) {
0962:                        isUnchanged = false;
0963:
0964:                        // build out error message
0965:                        buildTotalChangeErrorMessage(
0966:                                SOURCE_ACCOUNTING_LINE_ERRORS,
0967:                                persistedSourceLineTotal,
0968:                                currentSourceLineTotal);
0969:                    }
0970:
0971:                    if (currentTargetLineTotal
0972:                            .compareTo(persistedTargetLineTotal) != 0) {
0973:                        isUnchanged = false;
0974:
0975:                        // build out error message
0976:                        buildTotalChangeErrorMessage(
0977:                                TARGET_ACCOUNTING_LINE_ERRORS,
0978:                                persistedTargetLineTotal,
0979:                                currentTargetLineTotal);
0980:                    }
0981:                }
0982:
0983:                LOG
0984:                        .debug("isAccountingLineTotalsUnchanged(AccountingDocument) - end");
0985:                return isUnchanged;
0986:            }
0987:
0988:            /**
0989:             * attempt to retrieve the document from the DB for comparison
0990:             * 
0991:             * @param accountingDocument
0992:             * @return AccountingDocument
0993:             */
0994:            protected AccountingDocument retrievePersistedDocument(
0995:                    AccountingDocument accountingDocument) {
0996:                LOG
0997:                        .debug("retrievePersistedDocument(AccountingDocument) - start");
0998:
0999:                AccountingDocument persistedDocument = null;
1000:
1001:                try {
1002:                    persistedDocument = (AccountingDocument) SpringContext
1003:                            .getBean(DocumentService.class)
1004:                            .getByDocumentHeaderId(
1005:                                    accountingDocument.getDocumentNumber());
1006:                } catch (WorkflowException we) {
1007:                    LOG.error("retrievePersistedDocument(AccountingDocument)",
1008:                            we);
1009:
1010:                    handleNonExistentDocumentWhenApproving(accountingDocument);
1011:                }
1012:
1013:                LOG
1014:                        .debug("retrievePersistedDocument(AccountingDocument) - end");
1015:                return persistedDocument;
1016:            }
1017:
1018:            /**
1019:             * This method builds out the error message for when totals have changed.
1020:             * 
1021:             * @param propertyName
1022:             * @param persistedSourceLineTotal
1023:             * @param currentSourceLineTotal
1024:             */
1025:            protected void buildTotalChangeErrorMessage(String propertyName,
1026:                    KualiDecimal persistedSourceLineTotal,
1027:                    KualiDecimal currentSourceLineTotal) {
1028:                LOG
1029:                        .debug("buildTotalChangeErrorMessage(String, KualiDecimal, KualiDecimal) - start");
1030:
1031:                String persistedTotal = (String) new CurrencyFormatter()
1032:                        .format(persistedSourceLineTotal);
1033:                String currentTotal = (String) new CurrencyFormatter()
1034:                        .format(currentSourceLineTotal);
1035:                GlobalVariables
1036:                        .getErrorMap()
1037:                        .putError(
1038:                                propertyName,
1039:                                ERROR_DOCUMENT_SINGLE_ACCOUNTING_LINE_SECTION_TOTAL_CHANGED,
1040:                                new String[] { persistedTotal, currentTotal });
1041:
1042:                LOG
1043:                        .debug("buildTotalChangeErrorMessage(String, KualiDecimal, KualiDecimal) - end");
1044:            }
1045:
1046:            /**
1047:             * Handles the case when a non existent document is attempted to be retrieve and that if it's in an initiated state, it's ok.
1048:             * 
1049:             * @param accountingDocument
1050:             */
1051:            protected final void handleNonExistentDocumentWhenApproving(
1052:                    AccountingDocument accountingDocument) {
1053:                LOG
1054:                        .debug("handleNonExistentDocumentWhenApproving(AccountingDocument) - start");
1055:
1056:                // check to make sure this isn't an initiated document being blanket approved
1057:                if (!accountingDocument.getDocumentHeader()
1058:                        .getWorkflowDocument().stateIsInitiated()) {
1059:                    throw new IllegalStateException(
1060:                            "Document "
1061:                                    + accountingDocument.getDocumentNumber()
1062:                                    + " is not a valid document that currently exists in the system.");
1063:                }
1064:
1065:                LOG
1066:                        .debug("handleNonExistentDocumentWhenApproving(AccountingDocument) - end");
1067:            }
1068:
1069:            /**
1070:             * Checks accounting line number limits for routing. This method is for overriding by documents with rules about the total
1071:             * number of lines in both sections combined.
1072:             * 
1073:             * @param accountingDocument
1074:             * @return boolean True if the number of accounting lines are valid for routing, false otherwise.
1075:             */
1076:            protected boolean isAccountingLinesRequiredNumberForRoutingMet(
1077:                    AccountingDocument accountingDocument) {
1078:                LOG
1079:                        .debug("isAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - start");
1080:
1081:                boolean met = true;
1082:                met &= isSourceAccountingLinesRequiredNumberForRoutingMet(accountingDocument);
1083:                met &= isTargetAccountingLinesRequiredNumberForRoutingMet(accountingDocument);
1084:
1085:                LOG
1086:                        .debug("isAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1087:                return met;
1088:            }
1089:
1090:            /**
1091:             * Some double-sided documents also allow for one sided entries for correcting - so if one side is empty, the other side must
1092:             * have at least two lines in it. The balancing rules take care of validation of amounts.
1093:             * 
1094:             * @param accountingDocument
1095:             * @return boolean
1096:             */
1097:            protected boolean isOptionalOneSidedDocumentAccountingLinesRequiredNumberForRoutingMet(
1098:                    AccountingDocument accountingDocument) {
1099:                LOG
1100:                        .debug("isOptionalOneSidedDocumentAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - start");
1101:
1102:                int sourceSectionSize = accountingDocument
1103:                        .getSourceAccountingLines().size();
1104:                int targetSectionSize = accountingDocument
1105:                        .getTargetAccountingLines().size();
1106:
1107:                if ((sourceSectionSize == 0 && targetSectionSize < 2)
1108:                        || (targetSectionSize == 0 && sourceSectionSize < 2)) {
1109:                    GlobalVariables
1110:                            .getErrorMap()
1111:                            .putError(
1112:                                    ACCOUNTING_LINE_ERRORS,
1113:                                    ERROR_DOCUMENT_OPTIONAL_ONE_SIDED_DOCUMENT_REQUIRED_NUMBER_OF_ACCOUNTING_LINES_NOT_MET);
1114:
1115:                    LOG
1116:                            .debug("isOptionalOneSidedDocumentAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1117:                    return false;
1118:                }
1119:
1120:                LOG
1121:                        .debug("isOptionalOneSidedDocumentAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1122:                return true;
1123:            }
1124:
1125:            /**
1126:             * This method checks the amount of a given accounting line to make sure it's not 0, it's positive for regular documents, and
1127:             * negative for correction documents.
1128:             * 
1129:             * @param document
1130:             * @param accountingLine
1131:             * @return boolean True if there aren't any issues, false otherwise.
1132:             */
1133:            public boolean isAmountValid(AccountingDocument document,
1134:                    AccountingLine accountingLine) {
1135:                LOG
1136:                        .debug("isAmountValid(AccountingDocument, AccountingLine) - start");
1137:
1138:                KualiDecimal amount = accountingLine.getAmount();
1139:
1140:                // Check for zero amount, or negative on original (non-correction) document; no sign check for documents that are
1141:                // corrections to previous documents
1142:                String correctsDocumentId = document.getDocumentHeader()
1143:                        .getFinancialDocumentInErrorNumber();
1144:                if (ZERO.compareTo(amount) == 0) { // amount == 0
1145:                    GlobalVariables.getErrorMap().putError(
1146:                            AMOUNT_PROPERTY_NAME, ERROR_ZERO_AMOUNT,
1147:                            "an accounting line");
1148:                    LOG.info("failing isAmountValid - zero check");
1149:                    return false;
1150:                } else {
1151:                    if (null == correctsDocumentId
1152:                            && ZERO.compareTo(amount) == 1) { // amount < 0
1153:                        GlobalVariables.getErrorMap().putError(
1154:                                AMOUNT_PROPERTY_NAME,
1155:                                ERROR_INVALID_NEGATIVE_AMOUNT_NON_CORRECTION);
1156:                        LOG
1157:                                .info("failing isAmountValid - correctsDocumentId check && amount == 1");
1158:                        return false;
1159:                    }
1160:                }
1161:
1162:                return true;
1163:            }
1164:
1165:            /**
1166:             * This method will check to make sure that the required number of target accounting lines for routing, exist in the document.
1167:             * This method represents the default implementation, which is that at least one target accounting line must exist.
1168:             * 
1169:             * @param accountingDocument
1170:             * @return isOk
1171:             */
1172:            protected boolean isTargetAccountingLinesRequiredNumberForRoutingMet(
1173:                    AccountingDocument accountingDocument) {
1174:                LOG
1175:                        .debug("isTargetAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - start");
1176:
1177:                if (0 == accountingDocument.getTargetAccountingLines().size()) {
1178:                    GlobalVariables.getErrorMap().putError(
1179:                            TARGET_ACCOUNTING_LINE_ERRORS,
1180:                            ERROR_DOCUMENT_TARGET_SECTION_NO_ACCOUNTING_LINES,
1181:                            new String[] { accountingDocument
1182:                                    .getTargetAccountingLinesSectionTitle() });
1183:
1184:                    LOG
1185:                            .debug("isTargetAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1186:                    return false;
1187:                } else {
1188:                    LOG
1189:                            .debug("isTargetAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1190:                    return true;
1191:                }
1192:            }
1193:
1194:            /**
1195:             * This method will check to make sure that the required number of source accounting lines for routing, exist in the document.
1196:             * This method represents the default implementation, which is that at least one source accounting line must exist.
1197:             * 
1198:             * @param accountingDocument
1199:             * @return isOk
1200:             */
1201:            protected boolean isSourceAccountingLinesRequiredNumberForRoutingMet(
1202:                    AccountingDocument accountingDocument) {
1203:                LOG
1204:                        .debug("isSourceAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - start");
1205:
1206:                if (0 == accountingDocument.getSourceAccountingLines().size()) {
1207:                    GlobalVariables.getErrorMap().putError(
1208:                            SOURCE_ACCOUNTING_LINE_ERRORS,
1209:                            ERROR_DOCUMENT_SOURCE_SECTION_NO_ACCOUNTING_LINES,
1210:                            new String[] { accountingDocument
1211:                                    .getSourceAccountingLinesSectionTitle() });
1212:
1213:                    LOG
1214:                            .debug("isSourceAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1215:                    return false;
1216:                } else {
1217:                    LOG
1218:                            .debug("isSourceAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1219:                    return true;
1220:                }
1221:            }
1222:
1223:            /**
1224:             * This is the default implementation for Transactional Documents, which sums the amounts of all of the Debit GLPEs, and
1225:             * compares it to the sum of all of the Credit GLPEs. In general, this algorithm works, but it does not work for some specific
1226:             * documents such as the Journal Voucher. The method name denotes not an expected behavior, but a more general title so that
1227:             * some documents that don't use this default implementation, can override just this method without having to override the
1228:             * calling method.
1229:             * 
1230:             * @param accountingDocument
1231:             * @return boolean True if the document is balanced, false otherwise.
1232:             */
1233:            protected boolean isDocumentBalanceValid(
1234:                    AccountingDocument accountingDocument) {
1235:                LOG.debug("isDocumentBalanceValid(AccountingDocument) - start");
1236:
1237:                boolean returnboolean = isDocumentBalanceValidConsideringDebitsAndCredits(accountingDocument);
1238:                LOG.debug("isDocumentBalanceValid(AccountingDocument) - end");
1239:                return returnboolean;
1240:            }
1241:
1242:            /**
1243:             * This method sums all of the debit GLPEs up and sums all of the credit GLPEs up and then compares the totals to each other,
1244:             * returning true if they are equal and false if they are not.
1245:             * 
1246:             * @param accountingDocument
1247:             * @return boolean
1248:             */
1249:            protected boolean isDocumentBalanceValidConsideringDebitsAndCredits(
1250:                    AccountingDocument accountingDocument) {
1251:                LOG
1252:                        .debug("isDocumentBalanceValidConsideringDebitsAndCredits(AccountingDocument) - start");
1253:
1254:                // generate GLPEs specifically here so that we can compare debits to credits
1255:                if (!SpringContext
1256:                        .getBean(GeneralLedgerPendingEntryService.class)
1257:                        .generateGeneralLedgerPendingEntries(accountingDocument)) {
1258:                    throw new ValidationException(
1259:                            "general ledger GLPE generation failed");
1260:                }
1261:
1262:                // now loop through all of the GLPEs and calculate buckets for debits and credits
1263:                KualiDecimal creditAmount = new KualiDecimal(0);
1264:                KualiDecimal debitAmount = new KualiDecimal(0);
1265:                Iterator i = accountingDocument
1266:                        .getGeneralLedgerPendingEntries().iterator();
1267:                while (i.hasNext()) {
1268:                    GeneralLedgerPendingEntry glpe = (GeneralLedgerPendingEntry) i
1269:                            .next();
1270:                    if (!glpe.isTransactionEntryOffsetIndicator()) { // make sure we are looking at only the explicit entries
1271:                        if (KFSConstants.GL_CREDIT_CODE.equals(glpe
1272:                                .getTransactionDebitCreditCode())) {
1273:                            creditAmount = creditAmount.add(glpe
1274:                                    .getTransactionLedgerEntryAmount());
1275:                        } else { // DEBIT
1276:                            debitAmount = debitAmount.add(glpe
1277:                                    .getTransactionLedgerEntryAmount());
1278:                        }
1279:                    }
1280:                }
1281:                boolean isValid = debitAmount.compareTo(creditAmount) == 0;
1282:
1283:                if (!isValid) {
1284:                    GlobalVariables.getErrorMap().putError(
1285:                            ACCOUNTING_LINE_ERRORS, ERROR_DOCUMENT_BALANCE);
1286:                }
1287:
1288:                LOG
1289:                        .debug("isDocumentBalanceValidConsideringDebitsAndCredits(AccountingDocument) - end");
1290:                return isValid;
1291:            }
1292:
1293:            // Other Helper Methods
1294:            /**
1295:             * This populates an empty GeneralLedgerPendingEntry explicitEntry object instance with default values.
1296:             * 
1297:             * @param accountingDocument
1298:             * @param accountingLine
1299:             * @param sequenceHelper
1300:             * @param explicitEntry
1301:             */
1302:            protected void populateExplicitGeneralLedgerPendingEntry(
1303:                    AccountingDocument accountingDocument,
1304:                    AccountingLine accountingLine,
1305:                    GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
1306:                    GeneralLedgerPendingEntry explicitEntry) {
1307:                LOG
1308:                        .debug("populateExplicitGeneralLedgerPendingEntry(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper, GeneralLedgerPendingEntry) - start");
1309:
1310:                explicitEntry.setFinancialDocumentTypeCode(SpringContext
1311:                        .getBean(DocumentTypeService.class)
1312:                        .getDocumentTypeCodeByClass(
1313:                                accountingDocument.getClass()));
1314:                explicitEntry.setVersionNumber(new Long(1));
1315:                explicitEntry
1316:                        .setTransactionLedgerEntrySequenceNumber(new Integer(
1317:                                sequenceHelper.getSequenceCounter()));
1318:                Timestamp transactionTimestamp = new Timestamp(SpringContext
1319:                        .getBean(DateTimeService.class).getCurrentDate()
1320:                        .getTime());
1321:                explicitEntry.setTransactionDate(new java.sql.Date(
1322:                        transactionTimestamp.getTime()));
1323:                explicitEntry.setTransactionEntryProcessedTs(new java.sql.Date(
1324:                        transactionTimestamp.getTime()));
1325:                explicitEntry.setAccountNumber(accountingLine
1326:                        .getAccountNumber());
1327:                if (accountingLine.getAccount().getAccountSufficientFundsCode() == null) {
1328:                    accountingLine.getAccount().setAccountSufficientFundsCode(
1329:                            KFSConstants.SF_TYPE_NO_CHECKING);
1330:                }
1331:                explicitEntry.setAcctSufficientFundsFinObjCd(SpringContext
1332:                        .getBean(SufficientFundsService.class)
1333:                        .getSufficientFundsObjectCode(
1334:                                accountingLine.getObjectCode(),
1335:                                accountingLine.getAccount()
1336:                                        .getAccountSufficientFundsCode()));
1337:                explicitEntry
1338:                        .setFinancialDocumentApprovedCode(GENERAL_LEDGER_PENDING_ENTRY_CODE.NO);
1339:                explicitEntry.setTransactionEncumbranceUpdateCode(BLANK_SPACE);
1340:                explicitEntry.setFinancialBalanceTypeCode(BALANCE_TYPE_ACTUAL); // this is the default that most documents use
1341:                explicitEntry.setChartOfAccountsCode(accountingLine
1342:                        .getChartOfAccountsCode());
1343:                explicitEntry
1344:                        .setTransactionDebitCreditCode(isDebit(
1345:                                accountingDocument, accountingLine) ? KFSConstants.GL_DEBIT_CODE
1346:                                : KFSConstants.GL_CREDIT_CODE);
1347:                explicitEntry
1348:                        .setFinancialSystemOriginationCode(SpringContext
1349:                                .getBean(HomeOriginationService.class)
1350:                                .getHomeOrigination()
1351:                                .getFinSystemHomeOriginationCode());
1352:                explicitEntry.setDocumentNumber(accountingLine
1353:                        .getDocumentNumber());
1354:                explicitEntry.setFinancialObjectCode(accountingLine
1355:                        .getFinancialObjectCode());
1356:                ObjectCode objectCode = accountingLine.getObjectCode();
1357:                if (ObjectUtils.isNull(objectCode)) {
1358:                    accountingLine.refreshReferenceObject("objectCode");
1359:                }
1360:                explicitEntry.setFinancialObjectTypeCode(accountingLine
1361:                        .getObjectCode().getFinancialObjectTypeCode());
1362:                explicitEntry.setOrganizationDocumentNumber(accountingDocument
1363:                        .getDocumentHeader().getOrganizationDocumentNumber());
1364:                explicitEntry.setOrganizationReferenceId(accountingLine
1365:                        .getOrganizationReferenceId());
1366:                explicitEntry.setProjectCode(getEntryValue(accountingLine
1367:                        .getProjectCode(), GENERAL_LEDGER_PENDING_ENTRY_CODE
1368:                        .getBlankProjectCode()));
1369:                explicitEntry
1370:                        .setReferenceFinancialDocumentNumber(getEntryValue(
1371:                                accountingLine.getReferenceNumber(),
1372:                                BLANK_SPACE));
1373:                explicitEntry
1374:                        .setReferenceFinancialDocumentTypeCode(getEntryValue(
1375:                                accountingLine.getReferenceTypeCode(),
1376:                                BLANK_SPACE));
1377:                explicitEntry
1378:                        .setReferenceFinancialSystemOriginationCode(getEntryValue(
1379:                                accountingLine.getReferenceOriginCode(),
1380:                                BLANK_SPACE));
1381:                explicitEntry.setSubAccountNumber(getEntryValue(accountingLine
1382:                        .getSubAccountNumber(),
1383:                        GENERAL_LEDGER_PENDING_ENTRY_CODE
1384:                                .getBlankSubAccountNumber()));
1385:                explicitEntry.setFinancialSubObjectCode(getEntryValue(
1386:                        accountingLine.getFinancialSubObjectCode(),
1387:                        GENERAL_LEDGER_PENDING_ENTRY_CODE
1388:                                .getBlankFinancialSubObjectCode()));
1389:                explicitEntry.setTransactionEntryOffsetIndicator(false);
1390:                explicitEntry
1391:                        .setTransactionLedgerEntryAmount(getGeneralLedgerPendingEntryAmountForAccountingLine(accountingLine));
1392:                explicitEntry
1393:                        .setTransactionLedgerEntryDescription(getEntryValue(
1394:                                accountingLine
1395:                                        .getFinancialDocumentLineDescription(),
1396:                                accountingDocument.getDocumentHeader()
1397:                                        .getFinancialDocumentDescription()));
1398:                explicitEntry.setUniversityFiscalPeriodCode(null); // null here, is assigned during batch or in specific document rule
1399:                // classes
1400:                explicitEntry.setUniversityFiscalYear(accountingDocument
1401:                        .getPostingYear());
1402:                // TODO wait for core budget year data structures to be put in place
1403:                // explicitEntry.setBudgetYear(accountingLine.getBudgetYear());
1404:                // explicitEntry.setBudgetYearFundingSourceCode(budgetYearFundingSourceCode);
1405:
1406:                LOG
1407:                        .debug("populateExplicitGeneralLedgerPendingEntry(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper, GeneralLedgerPendingEntry) - end");
1408:            }
1409:
1410:            /**
1411:             * This is responsible for properly negating the sign on an accounting line's amount when its associated document is an error
1412:             * correction.
1413:             * 
1414:             * @param accountingDocument
1415:             * @param accountingLine
1416:             */
1417:            private final void handleDocumentErrorCorrection(
1418:                    AccountingDocument accountingDocument,
1419:                    AccountingLine accountingLine) {
1420:                LOG
1421:                        .debug("handleDocumentErrorCorrection(AccountingDocument, AccountingLine) - start");
1422:
1423:                // If the document corrects another document, make sure the accounting line has the correct sign.
1424:                if ((null == accountingDocument.getDocumentHeader()
1425:                        .getFinancialDocumentInErrorNumber() && accountingLine
1426:                        .getAmount().isNegative())
1427:                        || (null != accountingDocument.getDocumentHeader()
1428:                                .getFinancialDocumentInErrorNumber() && accountingLine
1429:                                .getAmount().isPositive())) {
1430:                    accountingLine.setAmount(accountingLine.getAmount()
1431:                            .multiply(new KualiDecimal(1)));
1432:                }
1433:
1434:                LOG
1435:                        .debug("handleDocumentErrorCorrection(AccountingDocument, AccountingLine) - end");
1436:            }
1437:
1438:            /**
1439:             * Determines whether an accounting line is an asset line.
1440:             * 
1441:             * @param accountingLine
1442:             * @return boolean True if a line is an asset line.
1443:             */
1444:            public final boolean isAsset(AccountingLine accountingLine) {
1445:                LOG.debug("isAsset(AccountingLine) - start");
1446:
1447:                boolean returnboolean = isAssetTypeCode(AccountingDocumentRuleUtil
1448:                        .getObjectCodeTypeCodeWithoutSideEffects(accountingLine));
1449:                LOG.debug("isAsset(AccountingLine) - end");
1450:                return returnboolean;
1451:            }
1452:
1453:            /**
1454:             * Determines whether an accounting line is a liability line.
1455:             * 
1456:             * @param accountingLine
1457:             * @return boolean True if the line is a liability line.
1458:             */
1459:            public final boolean isLiability(AccountingLine accountingLine) {
1460:                LOG.debug("isLiability(AccountingLine) - start");
1461:
1462:                boolean returnboolean = isLiabilityTypeCode(AccountingDocumentRuleUtil
1463:                        .getObjectCodeTypeCodeWithoutSideEffects(accountingLine));
1464:                LOG.debug("isLiability(AccountingLine) - end");
1465:                return returnboolean;
1466:            }
1467:
1468:            /**
1469:             * Determines whether an accounting line is an income line or not. This goes agains the configurable object type code list in
1470:             * the ApplicationParameter mechanism. This list can be configured externally.
1471:             * 
1472:             * @param accountingLine
1473:             * @return boolean True if the line is an income line.
1474:             */
1475:            public final boolean isIncome(AccountingLine accountingLine) {
1476:                LOG.debug("isIncome(AccountingLine) - start");
1477:
1478:                boolean returnboolean = AccountingDocumentRuleUtil
1479:                        .isIncome(accountingLine);
1480:                LOG.debug("isIncome(AccountingLine) - end");
1481:                return returnboolean;
1482:            }
1483:
1484:            /**
1485:             * Check object code type to determine whether the accounting line is expense.
1486:             * 
1487:             * @param accountingLine
1488:             * @return boolean True if the line is an expense line.
1489:             */
1490:            public boolean isExpense(AccountingLine accountingLine) {
1491:                LOG.debug("isExpense(AccountingLine) - start");
1492:
1493:                boolean returnboolean = AccountingDocumentRuleUtil
1494:                        .isExpense(accountingLine);
1495:                LOG.debug("isExpense(AccountingLine) - end");
1496:                return returnboolean;
1497:            }
1498:
1499:            /**
1500:             * Determines whether an accounting line is an expense or asset.
1501:             * 
1502:             * @param line
1503:             * @return boolean True if it's an expense or asset.
1504:             */
1505:            public final boolean isExpenseOrAsset(AccountingLine line) {
1506:                LOG.debug("isExpenseOrAsset(AccountingLine) - start");
1507:
1508:                boolean returnboolean = isAsset(line) || isExpense(line);
1509:                LOG.debug("isExpenseOrAsset(AccountingLine) - end");
1510:                return returnboolean;
1511:            }
1512:
1513:            /**
1514:             * Determines whether an accounting line is an income or liability line.
1515:             * 
1516:             * @param line
1517:             * @return boolean True if the line is an income or liability line.
1518:             */
1519:            public final boolean isIncomeOrLiability(AccountingLine line) {
1520:                LOG.debug("isIncomeOrLiability(AccountingLine) - start");
1521:
1522:                boolean returnboolean = isLiability(line) || isIncome(line);
1523:                LOG.debug("isIncomeOrLiability(AccountingLine) - end");
1524:                return returnboolean;
1525:            }
1526:
1527:            /**
1528:             * Check object code type to determine whether the accounting line is revenue.
1529:             * 
1530:             * @param line
1531:             * @return boolean True if the line is a revenue line.
1532:             */
1533:            public final boolean isRevenue(AccountingLine line) {
1534:                LOG.debug("isRevenue(AccountingLine) - start");
1535:
1536:                boolean returnboolean = !isExpense(line);
1537:                LOG.debug("isRevenue(AccountingLine) - end");
1538:                return returnboolean;
1539:            }
1540:
1541:            /**
1542:             * GLPE amounts are ALWAYS positive, so just take the absolute value of the accounting line's amount.
1543:             * 
1544:             * @param accountingLine
1545:             * @return KualiDecimal The amount that will be used to populate the GLPE.
1546:             */
1547:            protected KualiDecimal getGeneralLedgerPendingEntryAmountForAccountingLine(
1548:                    AccountingLine accountingLine) {
1549:                LOG
1550:                        .debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - start");
1551:
1552:                KualiDecimal returnKualiDecimal = accountingLine.getAmount()
1553:                        .abs();
1554:                LOG
1555:                        .debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - end");
1556:                return returnKualiDecimal;
1557:            }
1558:
1559:            /**
1560:             * Determines whether an accounting line represents a credit line.
1561:             * 
1562:             * @param accountingLine
1563:             * @param financialDocument
1564:             * @return boolean True if the line is a credit line.
1565:             * @throws IllegalStateException
1566:             */
1567:            public boolean isCredit(AccountingLine accountingLine,
1568:                    AccountingDocument financialDocument)
1569:                    throws IllegalStateException {
1570:                LOG
1571:                        .debug("isCredit(AccountingLine, AccountingDocument) - start");
1572:
1573:                boolean returnboolean = !isDebit(financialDocument,
1574:                        accountingLine);
1575:                LOG.debug("isCredit(AccountingLine, AccountingDocument) - end");
1576:                return returnboolean;
1577:            }
1578:
1579:            /**
1580:             * This method checks to see if the object code for the passed in accounting line exists in the list of restricted object codes.
1581:             * Note, the values that this checks against can be externally configured with the ApplicationParameter maintenance mechanism.
1582:             * 
1583:             * @param accountingLine
1584:             * @return boolean True if the use of the object code is allowed.
1585:             */
1586:            public boolean isObjectCodeAllowed(Class documentClass,
1587:                    AccountingLine accountingLine) {
1588:                return isAccountingLineValueAllowed(documentClass,
1589:                        accountingLine, RESTRICTED_OBJECT_CODES,
1590:                        KFSPropertyConstants.FINANCIAL_OBJECT_CODE,
1591:                        accountingLine.getFinancialObjectCode());
1592:            }
1593:
1594:            private boolean isAccountingLineValueAllowed(
1595:                    AccountingDocument accountingDocument,
1596:                    AccountingLine accountingLine, String parameterName,
1597:                    String propertyName) {
1598:                return isAccountingLineValueAllowed(accountingDocument
1599:                        .getClass(), accountingLine, parameterName,
1600:                        propertyName, propertyName);
1601:            }
1602:
1603:            private boolean isAccountingLineValueAllowed(Class documentClass,
1604:                    AccountingLine accountingLine, String parameterName,
1605:                    String propertyName, String userEnteredPropertyName) {
1606:                boolean isAllowed = true;
1607:                String exceptionMessage = "Invalue property name provided to AccountingDocumentRuleBase isAccountingLineValueAllowed method: "
1608:                        + propertyName;
1609:                try {
1610:                    String propertyValue = (String) PropertyUtils.getProperty(
1611:                            accountingLine, propertyName);
1612:                    if (getParameterService()
1613:                            .parameterExists(
1614:                                    ParameterConstants.FINANCIAL_PROCESSING_DOCUMENT.class,
1615:                                    parameterName)) {
1616:                        isAllowed = getParameterService()
1617:                                .getParameterEvaluator(
1618:                                        ParameterConstants.FINANCIAL_PROCESSING_DOCUMENT.class,
1619:                                        parameterName, propertyValue)
1620:                                .evaluateAndAddError(
1621:                                        SourceAccountingLine.class,
1622:                                        propertyName, userEnteredPropertyName);
1623:                    }
1624:                    if (getParameterService().parameterExists(documentClass,
1625:                            parameterName)) {
1626:                        isAllowed = getParameterService()
1627:                                .getParameterEvaluator(documentClass,
1628:                                        parameterName, propertyValue)
1629:                                .evaluateAndAddError(
1630:                                        SourceAccountingLine.class,
1631:                                        propertyName, userEnteredPropertyName);
1632:                    }
1633:                } catch (IllegalAccessException e) {
1634:                    throw new RuntimeException(exceptionMessage, e);
1635:                } catch (InvocationTargetException e) {
1636:                    throw new RuntimeException(exceptionMessage, e);
1637:                } catch (NoSuchMethodException e) {
1638:                    throw new RuntimeException(exceptionMessage, e);
1639:                }
1640:                return isAllowed;
1641:            }
1642:
1643:            /**
1644:             * This checks the accounting line's object type code to ensure that it is not a fund balance object type. This is a universal
1645:             * business rule that all transaction processing documents should abide by.
1646:             * 
1647:             * @param accountingLine
1648:             * @return boolean
1649:             */
1650:            public boolean isObjectTypeAllowed(Class documentClass,
1651:                    AccountingLine accountingLine) {
1652:                return isAccountingLineValueAllowed(documentClass,
1653:                        accountingLine, RESTRICTED_OBJECT_TYPE_CODES,
1654:                        "objectCode.financialObjectTypeCode",
1655:                        KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
1656:            }
1657:
1658:            /**
1659:             * This method checks to see if the fund group code for the accouting line's account is allowed. The common implementation
1660:             * allows any fund group code.
1661:             * 
1662:             * @param accountingLine
1663:             * @return boolean
1664:             */
1665:            public boolean isFundGroupAllowed(Class documentClass,
1666:                    AccountingLine accountingLine) {
1667:                return isAccountingLineValueAllowed(documentClass,
1668:                        accountingLine, RESTRICTED_FUND_GROUP_CODES,
1669:                        "account.subFundGroup.fundGroupCode", "accountNumber");
1670:            }
1671:
1672:            /**
1673:             * This method checks to see if the sub fund group code for the accounting line's account is allowed. The common implementation
1674:             * allows any sub fund group code.
1675:             * 
1676:             * @param accountingLine
1677:             * @return boolean
1678:             */
1679:            public boolean isSubFundGroupAllowed(Class documentClass,
1680:                    AccountingLine accountingLine) {
1681:                return isAccountingLineValueAllowed(documentClass,
1682:                        accountingLine, RESTRICTED_SUB_FUND_GROUP_CODES,
1683:                        "account.subFundGroupCode", "accountNumber");
1684:            }
1685:
1686:            /**
1687:             * This method checks to see if the object sub-type code for the accounting line's object code is allowed. The common
1688:             * implementation allows any object sub-type code.
1689:             * 
1690:             * @param accountingLine
1691:             * @return boolean True if the use of the object code's object sub type code is allowed; false otherwise.
1692:             */
1693:            public boolean isObjectSubTypeAllowed(Class documentClass,
1694:                    AccountingLine accountingLine) {
1695:                return isAccountingLineValueAllowed(documentClass,
1696:                        accountingLine, RESTRICTED_OBJECT_SUB_TYPE_CODES,
1697:                        "objectCode.financialObjectSubTypeCode",
1698:                        KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
1699:            }
1700:
1701:            /**
1702:             * This method checks to see if the object level for the accounting line's object code is allowed. The common implementation
1703:             * allows any object level.
1704:             * 
1705:             * @param accountingLine
1706:             * @return boolean True if the use of the object code's object sub type code is allowed; false otherwise.
1707:             */
1708:            public boolean isObjectLevelAllowed(Class documentClass,
1709:                    AccountingLine accountingLine) {
1710:                return isAccountingLineValueAllowed(documentClass,
1711:                        accountingLine, RESTRICTED_OBJECT_LEVELS,
1712:                        "objectCode.financialObjectLevelCode",
1713:                        KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
1714:            }
1715:
1716:            /**
1717:             * This method checks to see if the object consolidation for the accounting line's object code is allowed. The common
1718:             * implementation allows any object consolidation.
1719:             * 
1720:             * @param accountingLine
1721:             * @return boolean True if the use of the object code's object sub type code is allowed; false otherwise.
1722:             */
1723:            public boolean isObjectConsolidationAllowed(Class documentClass,
1724:                    AccountingLine accountingLine) {
1725:                return isAccountingLineValueAllowed(
1726:                        documentClass,
1727:                        accountingLine,
1728:                        RESTRICTED_OBJECT_CONSOLIDATIONS,
1729:                        "objectCode.financialObjectLevel.financialConsolidationObjectCode",
1730:                        KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
1731:            }
1732:
1733:            /**
1734:             * Determines whether the <code>objectTypeCode</code> is an asset.
1735:             * 
1736:             * @param objectTypeCode
1737:             * @return Is she asset or something completely different?
1738:             */
1739:            public final boolean isAssetTypeCode(String objectTypeCode) {
1740:                LOG.debug("isAssetTypeCode(String) - start");
1741:
1742:                boolean returnboolean = SpringContext.getBean(
1743:                        OptionsService.class).getCurrentYearOptions()
1744:                        .getFinancialObjectTypeAssetsCd()
1745:                        .equals(objectTypeCode);
1746:                LOG.debug("isAssetTypeCode(String) - end");
1747:                return returnboolean;
1748:            }
1749:
1750:            /**
1751:             * Determines whether the <code>objectTypeCode</code> is a liability.
1752:             * 
1753:             * @param objectTypeCode
1754:             * @return Is she liability or something completely different?
1755:             */
1756:            public final boolean isLiabilityTypeCode(String objectTypeCode) {
1757:                LOG.debug("isLiabilityTypeCode(String) - start");
1758:
1759:                boolean returnboolean = SpringContext.getBean(
1760:                        OptionsService.class).getCurrentYearOptions()
1761:                        .getFinObjectTypeLiabilitiesCode().equals(
1762:                                objectTypeCode);
1763:                LOG.debug("isLiabilityTypeCode(String) - end");
1764:                return returnboolean;
1765:            }
1766:
1767:            /**
1768:             * This method...
1769:             * 
1770:             * @param objectCode
1771:             * @return boolean
1772:             */
1773:            public final boolean isFundBalanceCode(String objectCode) {
1774:                LOG.debug("isFundBalanceCode(String) - start");
1775:
1776:                boolean returnboolean = (CONSOLIDATED_OBJECT_CODE.FUND_BALANCE
1777:                        .equals(objectCode));
1778:                LOG.debug("isFundBalanceCode(String) - end");
1779:                return returnboolean;
1780:            }
1781:
1782:            /**
1783:             * This method...
1784:             * 
1785:             * @param objectCode
1786:             * @return boolean
1787:             */
1788:            public final boolean isBudgetOnlyCodesSubType(String objectCode) {
1789:                LOG.debug("isBudgetOnlyCodesSubType(String) - start");
1790:
1791:                boolean returnboolean = (OBJECT_SUB_TYPE_CODE.BUDGET_ONLY
1792:                        .equals(objectCode));
1793:                LOG.debug("isBudgetOnlyCodesSubType(String) - end");
1794:                return returnboolean;
1795:            }
1796:
1797:            /**
1798:             * This method...
1799:             * 
1800:             * @param objectCode
1801:             * @return boolean
1802:             */
1803:            public final boolean isCashSubType(String objectCode) {
1804:                LOG.debug("isCashSubType(String) - start");
1805:
1806:                boolean returnboolean = (OBJECT_SUB_TYPE_CODE.CASH
1807:                        .equals(objectCode));
1808:                LOG.debug("isCashSubType(String) - end");
1809:                return returnboolean;
1810:            }
1811:
1812:            /**
1813:             * This method...
1814:             * 
1815:             * @param objectCode
1816:             * @return boolean
1817:             */
1818:            public final boolean isFundBalanceSubType(String objectCode) {
1819:                LOG.debug("isFundBalanceSubType(String) - start");
1820:
1821:                boolean returnboolean = (OBJECT_SUB_TYPE_CODE.SUBTYPE_FUND_BALANCE
1822:                        .equals(objectCode));
1823:                LOG.debug("isFundBalanceSubType(String) - end");
1824:                return returnboolean;
1825:            }
1826:
1827:            /**
1828:             * This method...
1829:             * 
1830:             * @param objectCode
1831:             * @return boolean
1832:             */
1833:            public final boolean isHourlyWagesSubType(String objectCode) {
1834:                LOG.debug("isHourlyWagesSubType(String) - start");
1835:
1836:                boolean returnboolean = (OBJECT_SUB_TYPE_CODE.HOURLY_WAGES
1837:                        .equals(objectCode));
1838:                LOG.debug("isHourlyWagesSubType(String) - end");
1839:                return returnboolean;
1840:            }
1841:
1842:            /**
1843:             * This method...
1844:             * 
1845:             * @param objectCode
1846:             * @return boolean
1847:             */
1848:            public final boolean isSalariesSubType(String objectCode) {
1849:                LOG.debug("isSalariesSubType(String) - start");
1850:
1851:                boolean returnboolean = (OBJECT_SUB_TYPE_CODE.SALARIES
1852:                        .equals(objectCode));
1853:                LOG.debug("isSalariesSubType(String) - end");
1854:                return returnboolean;
1855:            }
1856:
1857:            /**
1858:             * This method...
1859:             * 
1860:             * @param objectSubTypeCode
1861:             * @return boolean
1862:             */
1863:            public final boolean isValuationsAndAdjustmentsSubType(
1864:                    String objectSubTypeCode) {
1865:                LOG.debug("isValuationsAndAdjustmentsSubType(String) - start");
1866:
1867:                boolean returnboolean = (OBJECT_SUB_TYPE_CODE.VALUATIONS_AND_ADJUSTMENTS
1868:                        .equals(objectSubTypeCode));
1869:                LOG.debug("isValuationsAndAdjustmentsSubType(String) - end");
1870:                return returnboolean;
1871:            }
1872:
1873:            /**
1874:             * This method determines whether an object sub-type code is a mandatory transfer or not.
1875:             * 
1876:             * @param objectSubTypeCode
1877:             * @return True if it is a manadatory transfer, false otherwise.
1878:             */
1879:            public final boolean isMandatoryTransfersSubType(
1880:                    String objectSubTypeCode) {
1881:                LOG.debug("isMandatoryTransfersSubType(String) - start");
1882:
1883:                boolean returnboolean = checkMandatoryTransfersSubType(
1884:                        objectSubTypeCode,
1885:                        APPLICATION_PARAMETER.MANDATORY_TRANSFER_SUBTYPE_CODES);
1886:                LOG.debug("isMandatoryTransfersSubType(String) - end");
1887:                return returnboolean;
1888:            }
1889:
1890:            /**
1891:             * This method determines whether an object sub-type code is a non-mandatory transfer or not.
1892:             * 
1893:             * @param objectSubTypeCode
1894:             * @return True if it is a non-mandatory transfer, false otherwise.
1895:             */
1896:            public final boolean isNonMandatoryTransfersSubType(
1897:                    String objectSubTypeCode) {
1898:                LOG.debug("isNonMandatoryTransfersSubType(String) - start");
1899:
1900:                boolean returnboolean = checkMandatoryTransfersSubType(
1901:                        objectSubTypeCode,
1902:                        APPLICATION_PARAMETER.NONMANDATORY_TRANSFER_SUBTYPE_CODES);
1903:                LOG.debug("isNonMandatoryTransfersSubType(String) - end");
1904:                return returnboolean;
1905:            }
1906:
1907:            /**
1908:             * Helper method for checking the isMandatoryTransfersSubType() and isNonMandatoryTransfersSubType().
1909:             * 
1910:             * @param objectSubTypeCode
1911:             * @param parameterName
1912:             * @return boolean
1913:             */
1914:            private final boolean checkMandatoryTransfersSubType(
1915:                    String objectSubTypeCode, String parameterName) {
1916:                LOG
1917:                        .debug("checkMandatoryTransfersSubType(String, String) - start");
1918:
1919:                if (objectSubTypeCode == null) {
1920:                    throw new IllegalArgumentException(
1921:                            EXCEPTIONS.NULL_OBJECT_SUBTYPE_MESSAGE);
1922:                }
1923:                ParameterEvaluator evaluator = getParameterService()
1924:                        .getParameterEvaluator(
1925:                                ParameterConstants.FINANCIAL_PROCESSING_DOCUMENT.class,
1926:                                parameterName, objectSubTypeCode);
1927:                boolean returnboolean = evaluator.evaluationSucceeds();
1928:                LOG
1929:                        .debug("checkMandatoryTransfersSubType(String, String) - end");
1930:                return returnboolean;
1931:            }
1932:
1933:            /**
1934:             * @param objectCode
1935:             * @return boolean
1936:             */
1937:            public final boolean isFringeBenefitsSubType(String objectCode) {
1938:                LOG.debug("isFringeBenefitsSubType(String) - start");
1939:
1940:                boolean returnboolean = (OBJECT_SUB_TYPE_CODE.FRINGE_BEN
1941:                        .equals(objectCode));
1942:                LOG.debug("isFringeBenefitsSubType(String) - end");
1943:                return returnboolean;
1944:            }
1945:
1946:            /**
1947:             * @param objectCode
1948:             * @return boolean
1949:             */
1950:            public final boolean isCostRecoveryExpenseSubType(String objectCode) {
1951:                LOG.debug("isCostRecoveryExpenseSubType(String) - start");
1952:
1953:                boolean returnboolean = (OBJECT_SUB_TYPE_CODE.COST_RECOVERY_EXPENSE
1954:                        .equals(objectCode));
1955:                LOG.debug("isCostRecoveryExpenseSubType(String) - end");
1956:                return returnboolean;
1957:            }
1958:
1959:            /**
1960:             * This method will make sure that totals for a specified set of fund groups is valid across the two different accounting line
1961:             * sections.
1962:             * 
1963:             * @param tranDoc
1964:             * @param fundGroupCodes An array of the fund group codes that will be considered for balancing.
1965:             * @return True if they balance; false otherwise.
1966:             */
1967:            protected boolean isFundGroupSetBalanceValid(
1968:                    AccountingDocument tranDoc, Class componentClass,
1969:                    String parameterName) {
1970:                LOG
1971:                        .debug("isFundGroupSetBalanceValid(AccountingDocument, String[]) - start");
1972:
1973:                // don't need to do any of this if there's no parameter
1974:                if (!getParameterService().parameterExists(componentClass,
1975:                        parameterName)) {
1976:                    LOG
1977:                            .debug("isFundGroupSetBalanceValid(AccountingDocument, String[]) - end");
1978:                    return true;
1979:                }
1980:
1981:                List lines = new ArrayList();
1982:
1983:                lines.addAll(tranDoc.getSourceAccountingLines());
1984:                lines.addAll(tranDoc.getTargetAccountingLines());
1985:
1986:                KualiDecimal sourceLinesTotal = new KualiDecimal(0);
1987:                KualiDecimal targetLinesTotal = new KualiDecimal(0);
1988:
1989:                // iterate over each accounting line and if it has an account with a
1990:                // fund group that should be balanced, then add that lines amount to the bucket
1991:                for (Iterator i = lines.iterator(); i.hasNext();) {
1992:                    AccountingLine line = (AccountingLine) i.next();
1993:                    String fundGroupCode = line.getAccount().getSubFundGroup()
1994:                            .getFundGroupCode();
1995:
1996:                    ParameterEvaluator evaluator = getParameterService()
1997:                            .getParameterEvaluator(componentClass,
1998:                                    parameterName, fundGroupCode);
1999:                    if (evaluator.evaluationSucceeds()) {
2000:                        KualiDecimal glpeLineAmount = getGeneralLedgerPendingEntryAmountForAccountingLine(line);
2001:                        if (line.isSourceAccountingLine()) {
2002:                            sourceLinesTotal = sourceLinesTotal
2003:                                    .add(glpeLineAmount);
2004:                        } else {
2005:                            targetLinesTotal = targetLinesTotal
2006:                                    .add(glpeLineAmount);
2007:                        }
2008:                    }
2009:                }
2010:
2011:                // check that the amounts balance across sections
2012:                boolean isValid = true;
2013:
2014:                if (sourceLinesTotal.compareTo(targetLinesTotal) != 0) {
2015:                    isValid = false;
2016:
2017:                    // creating an evaluator to just format the fund codes into a nice string
2018:                    ParameterEvaluator evaluator = getParameterService()
2019:                            .getParameterEvaluator(componentClass,
2020:                                    parameterName, "");
2021:                    GlobalVariables
2022:                            .getErrorMap()
2023:                            .putError(
2024:                                    "document.sourceAccountingLines",
2025:                                    ERROR_DOCUMENT_FUND_GROUP_SET_DOES_NOT_BALANCE,
2026:                                    new String[] {
2027:                                            tranDoc
2028:                                                    .getSourceAccountingLinesSectionTitle(),
2029:                                            tranDoc
2030:                                                    .getTargetAccountingLinesSectionTitle(),
2031:                                            evaluator
2032:                                                    .getParameterValuesForMessage() });
2033:                }
2034:
2035:                LOG
2036:                        .debug("isFundGroupSetBalanceValid(AccountingDocument, String[]) - end");
2037:                return isValid;
2038:            }
2039:
2040:            /**
2041:             * A helper method which builds out a human readable string of the fund group codes that were used for the balancing rule.
2042:             * 
2043:             * @param fundGroupCodes
2044:             * @return String
2045:             */
2046:            private String buildFundGroupCodeBalancingErrorMessage(
2047:                    String[] fundGroupCodes) {
2048:                // TODO: delete this method
2049:                LOG
2050:                        .debug("buildFundGroupCodeBalancingErrorMessage(String[]) - start");
2051:
2052:                String balancingFundGroups = "";
2053:                int arrayLen = fundGroupCodes.length;
2054:                if (arrayLen == 1) {
2055:                    balancingFundGroups = fundGroupCodes[0];
2056:                } else {
2057:                    if (arrayLen == 2) {
2058:                        balancingFundGroups = fundGroupCodes[0] + " or "
2059:                                + fundGroupCodes[1];
2060:                    } else {
2061:                        for (int i = 0; i < arrayLen; i++) {
2062:                            String balancingFundGroupCode = fundGroupCodes[i];
2063:                            if ((i + 1) == arrayLen) {
2064:                                balancingFundGroups = balancingFundGroups
2065:                                        + ", or " + balancingFundGroupCode;
2066:                            } else {
2067:                                balancingFundGroups = balancingFundGroups
2068:                                        + ", " + balancingFundGroupCode;
2069:                            }
2070:                        }
2071:                    }
2072:                }
2073:
2074:                LOG
2075:                        .debug("buildFundGroupCodeBalancingErrorMessage(String[]) - end");
2076:                return balancingFundGroups;
2077:            }
2078:
2079:            /**
2080:             * Convience method for determine if a document is an error correction document.
2081:             * 
2082:             * @param accountingDocument
2083:             * @return true if document is an error correct
2084:             */
2085:            protected boolean isErrorCorrection(
2086:                    AccountingDocument accountingDocument) {
2087:                LOG.debug("isErrorCorrection(AccountingDocument) - start");
2088:
2089:                boolean isErrorCorrection = false;
2090:
2091:                String correctsDocumentId = accountingDocument
2092:                        .getDocumentHeader()
2093:                        .getFinancialDocumentInErrorNumber();
2094:                if (StringUtils.isNotBlank(correctsDocumentId)) {
2095:                    isErrorCorrection = true;
2096:                }
2097:
2098:                LOG.debug("isErrorCorrection(AccountingDocument) - end");
2099:                return isErrorCorrection;
2100:            }
2101:
2102:            /**
2103:             * util class that contains common algorithms for determining debit amounts
2104:             */
2105:            public static class IsDebitUtils {
2106:                public static final String isDebitCalculationIllegalStateExceptionMessage = "an invalid debit/credit check state was detected";
2107:                public static final String isErrorCorrectionIllegalStateExceptionMessage = "invalid (error correction) document not allowed";
2108:                public static final String isInvalidLineTypeIllegalArgumentExceptionMessage = "invalid accounting line type";
2109:
2110:                /**
2111:                 * @param debitCreditCode
2112:                 * @return true if debitCreditCode equals the the debit constant
2113:                 */
2114:                public static boolean isDebitCode(String debitCreditCode) {
2115:                    LOG.debug("isDebitCode(String) - start");
2116:
2117:                    boolean returnboolean = StringUtils.equals(
2118:                            KFSConstants.GL_DEBIT_CODE, debitCreditCode);
2119:                    LOG.debug("isDebitCode(String) - end");
2120:                    return returnboolean;
2121:                }
2122:
2123:                /**
2124:                 * <ol>
2125:                 * <li>object type is included in determining if a line is debit or credit.
2126:                 * </ol>
2127:                 * the following are credits (return false)
2128:                 * <ol>
2129:                 * <li> (isIncome || isLiability) && (lineAmount > 0)
2130:                 * <li> (isExpense || isAsset) && (lineAmount < 0)
2131:                 * </ol>
2132:                 * the following are debits (return true)
2133:                 * <ol>
2134:                 * <li> (isIncome || isLiability) && (lineAmount < 0)
2135:                 * <li> (isExpense || isAsset) && (lineAmount > 0)
2136:                 * </ol>
2137:                 * the following are invalid ( throws an <code>IllegalStateException</code>)
2138:                 * <ol>
2139:                 * <li> document isErrorCorrection
2140:                 * <li> lineAmount == 0
2141:                 * <li> ! (isIncome || isLiability || isExpense || isAsset)
2142:                 * </ol>
2143:                 * 
2144:                 * @param rule
2145:                 * @param accountingDocument
2146:                 * @param accountingLine
2147:                 * @return boolean
2148:                 */
2149:                public static boolean isDebitConsideringType(
2150:                        AccountingDocumentRuleBase rule,
2151:                        AccountingDocument accountingDocument,
2152:                        AccountingLine accountingLine) {
2153:                    LOG
2154:                            .debug("isDebitConsideringType(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - start");
2155:
2156:                    KualiDecimal amount = accountingLine.getAmount();
2157:                    // zero amounts are not allowed
2158:                    if (amount.isZero()) {
2159:                        throw new IllegalStateException(
2160:                                isDebitCalculationIllegalStateExceptionMessage);
2161:                    }
2162:                    boolean isDebit = false;
2163:                    boolean isPositiveAmount = accountingLine.getAmount()
2164:                            .isPositive();
2165:
2166:                    // income/liability
2167:                    if (rule.isIncomeOrLiability(accountingLine)) {
2168:                        isDebit = !isPositiveAmount;
2169:                    }
2170:                    // expense/asset
2171:                    else {
2172:                        if (rule.isExpenseOrAsset(accountingLine)) {
2173:                            isDebit = isPositiveAmount;
2174:                        } else {
2175:                            throw new IllegalStateException(
2176:                                    isDebitCalculationIllegalStateExceptionMessage);
2177:                        }
2178:                    }
2179:
2180:                    LOG
2181:                            .debug("isDebitConsideringType(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - end");
2182:                    return isDebit;
2183:                }
2184:
2185:                /**
2186:                 * <ol>
2187:                 * <li>object type is not included in determining if a line is debit or credit.
2188:                 * <li>accounting line section (source/target) is not included in determining if a line is debit or credit.
2189:                 * </ol>
2190:                 * the following are credits (return false)
2191:                 * <ol>
2192:                 * <li> none
2193:                 * </ol>
2194:                 * the following are debits (return true)
2195:                 * <ol>
2196:                 * <li> (isIncome || isLiability || isExpense || isAsset) && (lineAmount > 0)
2197:                 * </ol>
2198:                 * the following are invalid ( throws an <code>IllegalStateException</code>)
2199:                 * <ol>
2200:                 * <li> lineAmount <= 0
2201:                 * <li> ! (isIncome || isLiability || isExpense || isAsset)
2202:                 * </ol>
2203:                 * 
2204:                 * @param rule
2205:                 * @param accountingDocument
2206:                 * @param accountingLine
2207:                 * @return boolean
2208:                 */
2209:                public static boolean isDebitConsideringNothingPositiveOnly(
2210:                        AccountingDocumentRuleBase rule,
2211:                        AccountingDocument accountingDocument,
2212:                        AccountingLine accountingLine) {
2213:                    LOG
2214:                            .debug("isDebitConsideringNothingPositiveOnly(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - start");
2215:
2216:                    boolean isDebit = false;
2217:                    KualiDecimal amount = accountingLine.getAmount();
2218:                    boolean isPositiveAmount = amount.isPositive();
2219:                    // isDebit if income/liability/expense/asset and line amount is positive
2220:                    if (isPositiveAmount
2221:                            && (rule.isIncomeOrLiability(accountingLine) || rule
2222:                                    .isExpenseOrAsset(accountingLine))) {
2223:                        isDebit = true;
2224:                    } else {
2225:                        // non error correction
2226:                        if (!rule.isErrorCorrection(accountingDocument)) {
2227:                            throw new IllegalStateException(
2228:                                    isDebitCalculationIllegalStateExceptionMessage);
2229:
2230:                        }
2231:                        // error correction
2232:                        else {
2233:                            isDebit = false;
2234:                        }
2235:                    }
2236:
2237:                    LOG
2238:                            .debug("isDebitConsideringNothingPositiveOnly(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - end");
2239:                    return isDebit;
2240:                }
2241:
2242:                /**
2243:                 * <ol>
2244:                 * <li>accounting line section (source/target) type is included in determining if a line is debit or credit.
2245:                 * <li> zero line amounts are never allowed
2246:                 * </ol>
2247:                 * the following are credits (return false)
2248:                 * <ol>
2249:                 * <li> isSourceLine && (isIncome || isExpense || isAsset || isLiability) && (lineAmount > 0)
2250:                 * <li> isTargetLine && (isIncome || isExpense || isAsset || isLiability) && (lineAmount < 0)
2251:                 * </ol>
2252:                 * the following are debits (return true)
2253:                 * <ol>
2254:                 * <li> isSourceLine && (isIncome || isExpense || isAsset || isLiability) && (lineAmount < 0)
2255:                 * <li> isTargetLine && (isIncome || isExpense || isAsset || isLiability) && (lineAmount > 0)
2256:                 * </ol>
2257:                 * the following are invalid ( throws an <code>IllegalStateException</code>)
2258:                 * <ol>
2259:                 * <li> lineAmount == 0
2260:                 * <li> ! (isIncome || isLiability || isExpense || isAsset)
2261:                 * </ol>
2262:                 * 
2263:                 * @param rule
2264:                 * @param accountingDocument
2265:                 * @param accountingLine
2266:                 * @return boolean
2267:                 */
2268:                public static boolean isDebitConsideringSection(
2269:                        AccountingDocumentRuleBase rule,
2270:                        AccountingDocument accountingDocument,
2271:                        AccountingLine accountingLine) {
2272:                    LOG
2273:                            .debug("isDebitConsideringSection(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - start");
2274:
2275:                    KualiDecimal amount = accountingLine.getAmount();
2276:                    // zero amounts are not allowed
2277:                    if (amount.isZero()) {
2278:                        throw new IllegalStateException(
2279:                                isDebitCalculationIllegalStateExceptionMessage);
2280:                    }
2281:                    boolean isDebit = false;
2282:                    boolean isPositiveAmount = accountingLine.getAmount()
2283:                            .isPositive();
2284:                    // source line
2285:                    if (accountingLine.isSourceAccountingLine()) {
2286:                        // income/liability/expense/asset
2287:                        if (rule.isIncomeOrLiability(accountingLine)
2288:                                || rule.isExpenseOrAsset(accountingLine)) {
2289:                            isDebit = !isPositiveAmount;
2290:                        } else {
2291:                            throw new IllegalStateException(
2292:                                    isDebitCalculationIllegalStateExceptionMessage);
2293:                        }
2294:                    }
2295:                    // target line
2296:                    else {
2297:                        if (accountingLine.isTargetAccountingLine()) {
2298:                            if (rule.isIncomeOrLiability(accountingLine)
2299:                                    || rule.isExpenseOrAsset(accountingLine)) {
2300:                                isDebit = isPositiveAmount;
2301:                            } else {
2302:                                throw new IllegalStateException(
2303:                                        isDebitCalculationIllegalStateExceptionMessage);
2304:                            }
2305:                        } else {
2306:                            throw new IllegalArgumentException(
2307:                                    isInvalidLineTypeIllegalArgumentExceptionMessage);
2308:                        }
2309:                    }
2310:
2311:                    LOG
2312:                            .debug("isDebitConsideringSection(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - end");
2313:                    return isDebit;
2314:                }
2315:
2316:                /**
2317:                 * <ol>
2318:                 * <li>accounting line section (source/target) and object type is included in determining if a line is debit or credit.
2319:                 * <li> negative line amounts are <b>Only</b> allowed during error correction
2320:                 * </ol>
2321:                 * the following are credits (return false)
2322:                 * <ol>
2323:                 * <li> isSourceLine && (isExpense || isAsset) && (lineAmount > 0)
2324:                 * <li> isTargetLine && (isIncome || isLiability) && (lineAmount > 0)
2325:                 * <li> isErrorCorrection && isSourceLine && (isIncome || isLiability) && (lineAmount < 0)
2326:                 * <li> isErrorCorrection && isTargetLine && (isExpense || isAsset) && (lineAmount < 0)
2327:                 * </ol>
2328:                 * the following are debits (return true)
2329:                 * <ol>
2330:                 * <li> isSourceLine && (isIncome || isLiability) && (lineAmount > 0)
2331:                 * <li> isTargetLine && (isExpense || isAsset) && (lineAmount > 0)
2332:                 * <li> isErrorCorrection && (isExpense || isAsset) && (lineAmount < 0)
2333:                 * <li> isErrorCorrection && (isIncome || isLiability) && (lineAmount < 0)
2334:                 * </ol>
2335:                 * the following are invalid ( throws an <code>IllegalStateException</code>)
2336:                 * <ol>
2337:                 * <li> !isErrorCorrection && !(lineAmount > 0)
2338:                 * </ol>
2339:                 * 
2340:                 * @param rule
2341:                 * @param accountingDocument
2342:                 * @param accountingLine
2343:                 * @return boolean
2344:                 */
2345:                public static boolean isDebitConsideringSectionAndTypePositiveOnly(
2346:                        AccountingDocumentRuleBase rule,
2347:                        AccountingDocument accountingDocument,
2348:                        AccountingLine accountingLine) {
2349:                    LOG
2350:                            .debug("isDebitConsideringSectionAndTypePositiveOnly(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - start");
2351:
2352:                    boolean isDebit = false;
2353:                    KualiDecimal amount = accountingLine.getAmount();
2354:                    boolean isPositiveAmount = amount.isPositive();
2355:                    // non error correction - only allow amount >0
2356:                    if (!isPositiveAmount
2357:                            && !rule.isErrorCorrection(accountingDocument)) {
2358:                        throw new IllegalStateException(
2359:                                isDebitCalculationIllegalStateExceptionMessage);
2360:                    }
2361:                    // source line
2362:                    if (accountingLine.isSourceAccountingLine()) {
2363:                        // could write below block in one line using == as XNOR operator, but that's confusing to read:
2364:                        // isDebit = (rule.isIncomeOrLiability(accountingLine) == isPositiveAmount);
2365:                        if (isPositiveAmount) {
2366:                            isDebit = rule.isIncomeOrLiability(accountingLine);
2367:                        } else {
2368:                            isDebit = rule.isExpenseOrAsset(accountingLine);
2369:                        }
2370:                    }
2371:                    // target line
2372:                    else {
2373:                        if (accountingLine.isTargetAccountingLine()) {
2374:                            if (isPositiveAmount) {
2375:                                isDebit = rule.isExpenseOrAsset(accountingLine);
2376:                            } else {
2377:                                isDebit = rule
2378:                                        .isIncomeOrLiability(accountingLine);
2379:                            }
2380:                        } else {
2381:                            throw new IllegalArgumentException(
2382:                                    isInvalidLineTypeIllegalArgumentExceptionMessage);
2383:                        }
2384:                    }
2385:
2386:                    LOG
2387:                            .debug("isDebitConsideringSectionAndTypePositiveOnly(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - end");
2388:                    return isDebit;
2389:                }
2390:
2391:                /**
2392:                 * throws an <code>IllegalStateException</code> if the document is an error correction. otherwise does nothing
2393:                 * 
2394:                 * @param rule
2395:                 * @param accountingDocument
2396:                 */
2397:                public static void disallowErrorCorrectionDocumentCheck(
2398:                        AccountingDocumentRuleBase rule,
2399:                        AccountingDocument accountingDocument) {
2400:                    LOG
2401:                            .debug("disallowErrorCorrectionDocumentCheck(AccountingDocumentRuleBase, AccountingDocument) - start");
2402:
2403:                    if (rule.isErrorCorrection(accountingDocument)) {
2404:                        throw new IllegalStateException(
2405:                                isErrorCorrectionIllegalStateExceptionMessage);
2406:                    }
2407:
2408:                    LOG
2409:                            .debug("disallowErrorCorrectionDocumentCheck(AccountingDocumentRuleBase, AccountingDocument) - end");
2410:                }
2411:            }
2412:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.