/*
* Copyright (c) 2005-2011 Grameen Foundation USA
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* See also http://www.apache.org/licenses/LICENSE-2.0.html for an
* explanation of the license and how it is applied.
*/
package org.mifos.accounts.financial.business;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.mifos.application.admin.servicefacade.CoaDto;
import org.mifos.framework.business.AbstractBusinessObject;
/**
* Chart of Accounts.
*
* TODO: Rename this class to ChartOfAccountsBO (what else needs to change other
* than COABO.hbm.xml and the stuff which an Eclipse rename will find?).
*
* <p>Mifos's functional specification defines the Chart of Accounts (CoA) as "the list of accounts
* in the general ledger, systematically classified by title and number [the accounts General
* Ledger number, or GLCode]. In that context this class is misnamed, since an
* instance represents <em>one</em> account in the CoA.
*
* <p>To further add to the confusion, the term "category" sometimes refers to an account
* and at other times to a grouping of accounts. Mifos's default COA includes four
* categories, ASSET, EXPENDITURE, LIABILITY, and INCOME. Each has several subcategories in
* the default CoA.
*
* <p>Graphically speaking, the CoA is a forest of trees, headed by the four categories.
* Each account (COABO instance) can have zero or more subcategories (COABO instances)
* and zero or one parent (the head categories have no parent). If that's not confusing
* enough, only the head COABO instances have non-null <code>categoryType</code> fields, equal
* to one of the four strings listed above. The field is null for subcategories (child accounts).
*
* <p>Navigation around this graph is accomplished using two fields:
*
* <ul>
* <li><code>subCategories</code> represents the set of child accounts of this
* account, although technically it is a Set of COAHierarchyEntity.</li>
* <li><code>coaHierarchy</code> is an entity (<code>COAHierarchyEntity</code>) that
* points to the parent account of this account.</li>
* <ul>
*
* <p>This is not an optimal -- part of the navigation logic is in this class, and part
* is in the <code>COAHierarchyEntity</code>. The hierachical structure of the CoA should be separated
* from the accounts themselves. A better way would be to put all chart navigation
* into the <code>COAHierarchyEntity</code> instead of half and half.
*/
public class COABO extends AbstractBusinessObject {
private Short accountId;
private String accountName;
/**
* The Set of <code>COAHierarchyEntity</code> objects that are
* immediate children of this account. Does not include recursive
* descendants (see method getSubCategories). There is no setter for this
* field, it is populated by Hibernate -- see query "COABO.getAllCoa" in
* COABO.hbm.xml.
*
* TODO: retype this field as Set<COAHierarchyEntity>
*
*/
private Set subCategory;
private GLCodeEntity associatedGlcode;
private COAHierarchyEntity coaHierarchy;
/**
* Top-level general ledger accounts are also called "categories". If this
* instance is a "category", this property will be non-null. While the Java
* type is simply a String, accessors/mutators of this field shall
* <strong>only</strong> use/provide the GLCategoryType constant. A UserType
* could also be used to map {@link GLCategoryType} to this field, as seen
* in <a href="http://www.hibernate.org/288.html">these</a> <a
* href="http://www.hibernate.org/203.html">examples</a>, but this
* "lightweight" solution seemed Good Enough for the time being.
*/
private String categoryType;
protected COABO() {
super();
accountId = null;
accountName = null;
subCategory = new HashSet();
associatedGlcode = null;
coaHierarchy = null;
categoryType = null;
}
/**
* Only used in unit tests.
*/
public COABO(final int accountId, final String accountName) {
this.accountId = (short) accountId;
this.accountName = accountName;
}
/**
* Only used in unit tests.
*/
public COABO(final int accountId, final String accountName, final GLCodeEntity glCodeEntity) {
this.accountId = (short) accountId;
this.accountName = accountName;
this.associatedGlcode = glCodeEntity;
}
public COABO(final String accountName, final GLCodeEntity glCodeEntity) {
this.accountName = accountName;
this.associatedGlcode = glCodeEntity;
}
public Short getAccountId() {
return accountId;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(final String accountName) {
this.accountName = accountName;
}
public Set getSubCategory() {
return subCategory;
}
public GLCodeEntity getAssociatedGlcode() {
return associatedGlcode;
}
public COAHierarchyEntity getCoaHierarchy() {
return coaHierarchy;
}
public void setCoaHierarchy(final COAHierarchyEntity coaHierarchy) {
this.coaHierarchy = coaHierarchy;
}
public GLCategoryType getCategoryType() {
if (null == categoryType) {
return null;
}
return GLCategoryType.fromString(categoryType);
}
public void setCategoryType(final GLCategoryType categoryType) {
if (null == categoryType) {
this.categoryType = null;
} else {
this.categoryType = categoryType.toString();
}
}
/**
* As of 2009-10-27, this method is never used outside of test cases its use in
* getAssociatedChartOfAccounts, so should not be public.
*
* TODO Make this method private.
*
* @return the set of leaf (bottom-most) descendant accounts of this account,
* or an empty set if this account has no subcategories (children)
*
*/
public Set<COABO> getCurrentSubCategory() {
Set<COABO> applicableCOA = new HashSet<COABO>();
if (subCategory != null) {
Iterator<COAHierarchyEntity> iter = subCategory.iterator();
while (iter.hasNext()) {
COAHierarchyEntity coaHierarchy = iter.next();
applicableCOA.addAll(coaHierarchy.getCoa().getAssociatedChartOfAccounts());
}
}
return applicableCOA;
}
public String getGlCode() {
return getAssociatedGlcode().getGlcode();
}
/**
* @return a list of the immediate subcategories (immediate child accounts) of this account,
* ordered by GLCode. If the account has no subcategories, return an empty list.
*/
public List<COABO> getSubCategoryCOABOs() {
List<COABO> subCategories = new ArrayList<COABO>();
if (subCategory != null) {
for (COAHierarchyEntity hierarchyEntity : (Set<COAHierarchyEntity>) subCategory) {
subCategories.add(hierarchyEntity.getCoa());
}
}
Collections.sort(subCategories, new GLCodeComparator());
return subCategories;
}
public COABO getCOAHead() {
COAHierarchyEntity headHiearchy = null;
COAHierarchyEntity currentHiearchy = null;
currentHiearchy = coaHierarchy;
headHiearchy = currentHiearchy.getParentAccount();
while (headHiearchy != null) {
currentHiearchy = headHiearchy;
headHiearchy = headHiearchy.getParentAccount();
}
return currentHiearchy.getCoa();
}
/**
*
* @return If the account has no subcategories, return this account,
* otherwise return the leaf (bottom-most) descendants of this account.
*/
public Set<COABO> getAssociatedChartOfAccounts() {
Set<COABO> applicableCOA = new HashSet<COABO>();
if (null == subCategory || subCategory.size() == 0) {
applicableCOA.add(this);
} else {
applicableCOA.addAll(getCurrentSubCategory());
}
return applicableCOA;
}
/**
* convenience method gets the top-level accounting cateory
*/
public GLCategoryType getTopLevelCategoryType() {
return this.getCOAHead().getCategoryType();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
// if (getClass().)
if (!(obj instanceof COABO)) {
return false;
}
COABO other = (COABO) obj;
if (this.accountId == null) {
if (other.accountId != null) {
return false;
}
} else if (!this.accountId.equals(other.accountId)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.accountId == null) ? 0 : this.accountId.hashCode());
return result;
}
/**
* Compares general ledger codes lexicographically. Must use this type of
* comparison since GL codes may have numbers <em>and</em> letters.
*/
public class GLCodeComparator implements Comparator<COABO> {
@Override
public int compare(final COABO coa1, final COABO coa2) {
return coa1.getAssociatedGlcode().getGlcode().compareTo(coa2.getAssociatedGlcode().getGlcode());
}
}
public CoaDto toDto() {
CoaDto dto = new CoaDto();
dto.setAccountId(accountId);
dto.setAccountName(accountName);
dto.setGlCodeString(this.associatedGlcode.getGlcode());
COAHierarchyEntity parentHierarchy = coaHierarchy.getParentAccount();
if (parentHierarchy != null) {
COABO parent = parentHierarchy.getCoa();
dto.setParentGlCode(parent.getGlCode());
}
return dto;
}
}