/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.sys.businessobject; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.kuali.kfs.coa.businessobject.Account; import org.kuali.kfs.coa.businessobject.ObjectCode; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.AccountingDocument; import org.kuali.kfs.sys.document.service.AccountPresenceService; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.util.ObjectUtils; /** * This class helps implement AccountingLine overrides. It is not persisted itself, but it simplifies working with the persisted * codes. Instances break the code into components. Static methods help with the AccountingLine. */ public class AccountingLineOverride { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountingLineOverride.class); /** * These codes are the way the override is persisted in the AccountingLine. */ public static final class CODE { // todo: use JDK 1.5 enum public static final String BLANK = ""; public static final String NONE = "NONE"; public static final String EXPIRED_ACCOUNT = "EXPIRED_ACCOUNT"; public static final String NON_BUDGETED_OBJECT = "NON_BUDGETED_OBJECT"; public static final String TRANSACTION_EXCEEDS_REMAINING_BUDGET = "TRANSACTION_EXCEEDS_REMAINING_BUDGET"; public static final String EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT = "EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT"; public static final String NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET = "NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET"; public static final String EXPIRED_ACCOUNT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET = "EXPIRED_ACCOUNT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET"; public static final String EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET = "EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET"; public static final String NON_FRINGE_ACCOUNT_USED = "NON_FRINGE_ACCOUNT_USED"; public static final String EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED = "EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED"; } /** * These are the somewhat independent components of an override. */ public static final class COMPONENT { // todo: use JDK 1.5 enum public static final Integer EXPIRED_ACCOUNT = new Integer(1); public static final Integer NON_BUDGETED_OBJECT = new Integer(2); public static final Integer TRANSACTION_EXCEEDS_REMAINING_BUDGET = new Integer(3); public static final Integer NON_FRINGE_ACCOUNT_USED = new Integer(8); } /** * The names of the AccountingLine properties that the processForOutput() and determineNeededOverrides() methods use. Callers of * those methods may need to refresh these fields from OJB. */ public static final List<String> REFRESH_FIELDS = Collections.unmodifiableList(Arrays.asList(new String[] { "account", "objectCode" })); /** * This holds an instance of every valid override, mapped by code. */ private static final Map<String, AccountingLineOverride> codeToOverrideMap = new HashMap<String, AccountingLineOverride>(); /** * This holds an instance of every valid override, mapped by components. */ private static final Map componentsToOverrideMap = new HashMap(); static { // populate the code map new AccountingLineOverride(CODE.BLANK, new Integer[] {}); new AccountingLineOverride(CODE.NONE, new Integer[] {}); new AccountingLineOverride(CODE.EXPIRED_ACCOUNT, // todo: use JDK 1.5 ... args new Integer[] { COMPONENT.EXPIRED_ACCOUNT }); new AccountingLineOverride(CODE.NON_BUDGETED_OBJECT, new Integer[] { COMPONENT.NON_BUDGETED_OBJECT }); new AccountingLineOverride(CODE.TRANSACTION_EXCEEDS_REMAINING_BUDGET, new Integer[] { COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET }); new AccountingLineOverride(CODE.EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT, new Integer[] { COMPONENT.EXPIRED_ACCOUNT, COMPONENT.NON_BUDGETED_OBJECT }); new AccountingLineOverride(CODE.NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET, new Integer[] { COMPONENT.NON_BUDGETED_OBJECT, COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET }); new AccountingLineOverride(CODE.EXPIRED_ACCOUNT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET, new Integer[] { COMPONENT.EXPIRED_ACCOUNT, COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET }); new AccountingLineOverride(CODE.EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET, new Integer[] { COMPONENT.EXPIRED_ACCOUNT, COMPONENT.NON_BUDGETED_OBJECT, COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET }); new AccountingLineOverride(CODE.NON_FRINGE_ACCOUNT_USED, new Integer[] { COMPONENT.NON_FRINGE_ACCOUNT_USED }); new AccountingLineOverride(CODE.EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED, new Integer[] { COMPONENT.EXPIRED_ACCOUNT, COMPONENT.NON_FRINGE_ACCOUNT_USED }); } private final String code; private final Set components; /** * This private constructor is for the static initializer. * * @param myCode * @param myComponents */ private AccountingLineOverride(String myCode, Integer[] myComponents) { code = myCode; components = componentsAsSet(myComponents); codeToOverrideMap.put(code, this); componentsToOverrideMap.put(components, this); } /** * Checks whether this override contains the given component. * * @param component * @return whether this override contains the given component. */ public boolean hasComponent(Integer component) { return components.contains(component); } /** * Gets the code of this override. * * @return the code of this override. */ public String getCode() { return code; } /** * Gets the components of this override. * * @return the components of this override. */ private Set getComponents() { return components; } /** * @see java.lang.Object#toString() */ @Override public String toString() { return "AccountingLineOverride (code " + code + ", components " + components + ")"; } /** * Returns the AccountingLineOverride that has the components of this AccountingLineOverride minus any components not in the * given mask. This is like <code>&</code>(a bit-wise and), if the components were bits. * * @param mask * @return the AccountingLineOverride that has the components of this AccountingLineOverride minus any components not in the * given mask. * @throws IllegalArgumentException if there is no such valid combination of components */ public AccountingLineOverride mask(AccountingLineOverride mask) { Set key = maskComponents(mask); if (!isValidComponentSet(key)) { throw new IllegalArgumentException("invalid component set " + key); } return valueOf(key); } /** * Returns the Set of components that this override and the given override have in common. * * @param mask * @return the Set of components that this override and the given override have in common. */ private Set maskComponents(AccountingLineOverride mask) { Set retval = new HashSet(components); retval.retainAll(mask.getComponents()); return retval; } /** * Returns whether this override, when masked by the given override, is valid. Some combinations of components have no override * code defined. * * @param mask * @return whether this override, when masked by the given override, is valid. */ public boolean isValidMask(AccountingLineOverride mask) { return isValidComponentSet(maskComponents(mask)); } /** * Returns whether the given String is a valid override code. * * @param code * @return whether the given String is a valid override code. */ public static boolean isValidCode(String code) { return codeToOverrideMap.containsKey(code); } /** * Returns whether the given Integers are a valid set of components. Some combinations of components are invalid and have no * code defined. * * @param components * @return whether the given Integers are a valid set of components. */ public static boolean isValidComponentSet(Integer[] components) { return isValidComponentSet(componentsAsSet(components)); } private static boolean isValidComponentSet(Set components) { // todo: JDK 1.5 generic Set return componentsToOverrideMap.containsKey(components); } /** * Factory method from code. * * @param code the override code * @return the AccountingLineOverride instance corresponding to the given code. * @throws IllegalArgumentException if the given code is not valid */ public static AccountingLineOverride valueOf(String code) { if (!isValidCode(code)) { throw new IllegalArgumentException("invalid code " + code); } return codeToOverrideMap.get(code); // todo: JDK 1.5 generic Map instead of cast } /** * Factory method from components. * * @param components the override components, treated as a set * @return the AccountingLineOverride instance corresponding to the given component set. * @throws IllegalArgumentException if the given set of components is not valid */ public static AccountingLineOverride valueOf(Integer[] components) { Set key = componentsAsSet(components); if (!isValidComponentSet(key)) { throw new IllegalArgumentException("invalid component set " + key); } return valueOf(key); } public static AccountingLineOverride valueOf(Set components) { return (AccountingLineOverride) componentsToOverrideMap.get(components); // todo: JDK 1.5 generic Map instead of cast } private static Set componentsAsSet(Integer[] components) { return Collections.unmodifiableSet(new HashSet(Arrays.asList(components))); } /** * On the given AccountingLine, converts override input checkboxes from a Struts Form into a persistable override code. * * @param line */ public static void populateFromInput(AccountingLine line) { // todo: this logic won't work if a single account checkbox might also stands for NON_FRINGE_ACCOUNT_USED; needs thought Set overrideInputComponents = new HashSet(); if (line.getAccountExpiredOverride()) { overrideInputComponents.add(COMPONENT.EXPIRED_ACCOUNT); } if (line.isObjectBudgetOverride()) { overrideInputComponents.add(COMPONENT.NON_BUDGETED_OBJECT); } if (!isValidComponentSet(overrideInputComponents)) { // todo: error for invalid override checkbox combinations, for which there is no override code } line.setOverrideCode(valueOf(overrideInputComponents).getCode()); } /** * Prepares the given AccountingLine in a Struts Action for display by a JSP. This means converting the override code to * checkboxes for display and input, as well as analysing the accounting line and determining which override checkboxes are * needed. * * @param line */ public static void processForOutput(AccountingDocument document ,AccountingLine line) { AccountingLineOverride fromCurrentCode = valueOf(line.getOverrideCode()); AccountingLineOverride needed = determineNeededOverrides(document,line); line.setAccountExpiredOverride(fromCurrentCode.hasComponent(COMPONENT.EXPIRED_ACCOUNT)); line.setAccountExpiredOverrideNeeded(needed.hasComponent(COMPONENT.EXPIRED_ACCOUNT)); line.setObjectBudgetOverride(fromCurrentCode.hasComponent(COMPONENT.NON_BUDGETED_OBJECT)); line.setObjectBudgetOverrideNeeded(needed.hasComponent(COMPONENT.NON_BUDGETED_OBJECT)); } /** * Determines what overrides the given line needs. * * @param line * @return what overrides the given line needs. */ public static AccountingLineOverride determineNeededOverrides(AccountingDocument document ,AccountingLine line) { boolean isDocumentFinalOrProcessed = false; if(ObjectUtils.isNotNull(document)) { AccountingDocument accountingDocument = document; isDocumentFinalOrProcessed = accountingDocument.isDocumentFinalOrProcessed(); } Set neededOverrideComponents = new HashSet(); if (needsExpiredAccountOverride(line, isDocumentFinalOrProcessed)) { neededOverrideComponents.add(COMPONENT.EXPIRED_ACCOUNT); } if (needsObjectBudgetOverride(line.getAccount(), line.getObjectCode())) { neededOverrideComponents.add(COMPONENT.NON_BUDGETED_OBJECT); } if (!isValidComponentSet(neededOverrideComponents)) { // todo: error for invalid override checkbox combinations, for which there is no override code } return valueOf(neededOverrideComponents); } /** * Returns whether the given account needs an expired account override. * * @param account * @return whether the given account needs an expired account override. */ public static boolean needsExpiredAccountOverride(Account account) { return !ObjectUtils.isNull(account) && account.isActive() && account.isExpired(); } /** * Returns whether the given account needs an expired account override. * * @param account * @return whether the given account needs an expired account override. */ public static boolean needsExpiredAccountOverride(AccountingLine line, boolean isDocumentFinalOrProcessed ) { if(isDocumentFinalOrProcessed){ if(CODE.EXPIRED_ACCOUNT.equals(line.getOverrideCode())) { return true; } else { return false; } } else { return !ObjectUtils.isNull(line.getAccount()) && line.getAccount().isActive() && line.getAccount().isExpired(); } } /** * Returns whether the given account needs an expired account override. * * @param account * @return whether the given account needs an expired account override. */ public static boolean needsNonFringAccountOverride(Account account) { return !ObjectUtils.isNull(account) && account.isActive() && !account.isAccountsFringesBnftIndicator(); } /** * Returns whether the given object code needs an object budget override * * @param account * @return whether the given object code needs an object budget override */ public static boolean needsObjectBudgetOverride(Account account, ObjectCode objectCode) { return !ObjectUtils.isNull(account) && !ObjectUtils.isNull(objectCode) && account.isActive() && !SpringContext.getBean(AccountPresenceService.class).isObjectCodeBudgetedForAccountPresence(account, objectCode); } public static Document getDocument(AccountingLine line) { Document document = null; try { document = SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(line.getDocumentNumber()); }catch(WorkflowException exception) { LOG.error("Unable to locate document for documentId :: " + line.getDocumentNumber() ); } return document; } }