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