/*
* 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.fees.business;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.mifos.accounts.fees.exceptions.FeeException;
import org.mifos.accounts.fees.util.helpers.FeeCategory;
import org.mifos.accounts.fees.util.helpers.FeeChangeType;
import org.mifos.accounts.fees.util.helpers.FeeConstants;
import org.mifos.accounts.fees.util.helpers.FeeFrequencyType;
import org.mifos.accounts.fees.util.helpers.FeeLevel;
import org.mifos.accounts.fees.util.helpers.FeeStatus;
import org.mifos.accounts.fees.util.helpers.RateAmountFlag;
import org.mifos.accounts.financial.business.GLCodeEntity;
import org.mifos.accounts.persistence.LegacyAccountDao;
import org.mifos.application.master.persistence.LegacyMasterDao;
import org.mifos.application.meeting.business.MeetingBO;
import org.mifos.application.servicefacade.ApplicationContextProvider;
import org.mifos.config.AccountingRules;
import org.mifos.customers.office.business.OfficeBO;
import org.mifos.customers.office.persistence.OfficePersistence;
import org.mifos.dto.domain.FeeDto;
import org.mifos.dto.domain.FeeFrequencyDto;
import org.mifos.dto.domain.GLCodeDto;
import org.mifos.framework.business.AbstractBusinessObject;
import org.mifos.framework.exceptions.PersistenceException;
import org.mifos.framework.exceptions.PropertyNotFoundException;
import org.mifos.framework.util.helpers.Money;
import org.mifos.security.util.UserContext;
public abstract class FeeBO extends AbstractBusinessObject {
private final Short feeId;
private final String feeName;
private Short changeType;
private FeeStatusEntity feeStatus;
private final OfficeBO office;
private final CategoryTypeEntity categoryType;
private final FeeFrequencyEntity feeFrequency;
private final GLCodeEntity glCode;
private final Set<FeeLevelEntity> feeLevels;
private OfficePersistence officePersistence;
public OfficePersistence getOfficePersistence() {
if(officePersistence == null){
officePersistence = new OfficePersistence();
}
return officePersistence;
}
public void setOfficePersistence(final OfficePersistence officePersistence) {
this.officePersistence = officePersistence;
}
/**
* @param office
* @param createdByUserId
* @param createdDate
*
*/
public FeeBO(final String name, final FeeCategory category, final FeeFrequencyType feeFrequencyType,
final GLCodeEntity feeGLCode, final MeetingBO meetingPeriodicity, final OfficeBO office,
final Date createdDate,
final Short createdByUserId) {
this.feeId = null;
this.feeName = name;
this.categoryType = new CategoryTypeEntity(category);
this.glCode = feeGLCode;
this.feeLevels = new HashSet<FeeLevelEntity>();
this.changeType = FeeChangeType.NOT_UPDATED.getValue();
this.feeStatus = new FeeStatusEntity(FeeStatus.ACTIVE);
this.createdDate = createdDate;
this.createdBy = createdByUserId;
this.office = office;
FeePaymentEntity feePaymentEntity = null;
FeeFrequencyTypeEntity feeFrequencyTypeEntity = new FeeFrequencyTypeEntity(feeFrequencyType);
try {
this.feeFrequency = new FeeFrequencyEntity(feeFrequencyTypeEntity, this, feePaymentEntity,
meetingPeriodicity);
} catch (FeeException e) {
throw new IllegalStateException(e);
}
}
/**
* Constructor to create a valid Fee Object
*/
protected FeeBO(final UserContext userContext, final String feeName, final CategoryTypeEntity categoryType,
final FeeFrequencyTypeEntity feeFrequencyType, final GLCodeEntity glCodeEntity, final boolean isCustomerDefaultFee,
final FeePaymentEntity feePayment, final MeetingBO feeMeeting) throws FeeException {
validateFields(feeName, categoryType, glCodeEntity);
this.feeFrequency = new FeeFrequencyEntity(feeFrequencyType, this, feePayment, feeMeeting);
setUserContext(userContext);
setCreateDetails();
this.feeName = feeName;
this.categoryType = categoryType;
this.glCode = glCodeEntity;
this.feeId = null;
this.feeLevels = new HashSet<FeeLevelEntity>();
try {
this.office = getOfficePersistence().getHeadOffice();
} catch (PersistenceException e) {
throw new FeeException(e);
}
this.changeType = FeeChangeType.NOT_UPDATED.getValue();
this.setFeeStatus(retrieveFeeStatusEntity(FeeStatus.ACTIVE));
if (isCustomerDefaultFee) {
makeFeeDefaultToCustomer();
}
}
/**
* Adding a default constructor is hibernate's requirement and should not be
* used to create a valid Fee object.
*/
protected FeeBO() {
this.feeId = null;
this.office = null;
this.feeName = null;
this.categoryType = null;
this.feeFrequency = null;
this.glCode = null;
this.feeLevels = null;
}
public Short getFeeId() {
return feeId;
}
public OfficeBO getOffice() {
return office;
}
public String getFeeName() {
return feeName;
}
public CategoryTypeEntity getCategoryType() {
return categoryType;
}
public FeeFrequencyEntity getFeeFrequency() {
return feeFrequency;
}
public Set<FeeLevelEntity> getFeeLevels() {
return feeLevels;
}
public FeeStatusEntity getFeeStatus() {
return feeStatus;
}
public GLCodeEntity getGlCode() {
return glCode;
}
public FeeChangeType getFeeChangeType() throws PropertyNotFoundException {
return FeeChangeType.getFeeChangeType(this.changeType);
}
public void updateFeeChangeType(final FeeChangeType updateFlag) {
this.changeType = updateFlag.getValue();
}
private void setFeeStatus(final FeeStatusEntity feeStatus) {
this.feeStatus = feeStatus;
}
public void updateStatus(final FeeStatus status) throws FeeException {
if (!this.feeStatus.getId().equals(status.getValue())) {
setFeeStatus(retrieveFeeStatusEntity(status));
}
}
public void update() throws FeeException {
try {
setUpdateDetails();
ApplicationContextProvider.getBean(LegacyAccountDao.class).createOrUpdate(this);
} catch (PersistenceException e) {
throw new FeeException(FeeConstants.FEE_UPDATE_ERROR, e);
}
}
public abstract RateAmountFlag getFeeType();
public void save() throws FeeException {
try {
ApplicationContextProvider.getBean(LegacyAccountDao.class).createOrUpdate(this);
} catch (PersistenceException he) {
throw new FeeException(FeeConstants.FEE_CREATE_ERROR, he);
}
}
public boolean isActive() {
return getFeeStatus().isActive();
}
public boolean isPeriodic() {
return getFeeFrequency().isPeriodic();
}
public boolean isOneTime() {
return getFeeFrequency().isOneTime();
}
public boolean isTimeOfDisbursement() {
return getFeeFrequency().isTimeOfDisbursement();
}
/**
* Returns true if any fees applied to an installment might be an amount
* that exceeds the precision specified by initial or final rounding of
* amounts in an installment. This check is required temporarily in order to
* prevent re-rounding when a fee is added after payments have been made.
*/
public abstract boolean doesFeeInvolveFractionalAmounts();
public boolean isCustomerDefaultFee() {
return getFeeLevels().size() > 0;
}
protected void validateFields(final String feeName, final CategoryTypeEntity categoryType, final GLCodeEntity glCode)
throws FeeException {
validateFeeName(feeName);
validateFeeCateogry(categoryType);
validateGLCode(glCode);
}
private void makeFeeDefaultToCustomer() throws FeeException {
try {
if (getCategoryType().getFeeCategory().equals(FeeCategory.CLIENT)) {
addFeeLevel(createFeeLevel(FeeLevel.CLIENTLEVEL));
} else if (getCategoryType().getFeeCategory().equals(FeeCategory.GROUP)) {
addFeeLevel(createFeeLevel(FeeLevel.GROUPLEVEL));
} else if (getCategoryType().getFeeCategory().equals(FeeCategory.CENTER)) {
addFeeLevel(createFeeLevel(FeeLevel.CENTERLEVEL));
} else if (getCategoryType().getFeeCategory().equals(FeeCategory.ALLCUSTOMERS)) {
addFeeLevel(createFeeLevel(FeeLevel.CLIENTLEVEL));
addFeeLevel(createFeeLevel(FeeLevel.GROUPLEVEL));
addFeeLevel(createFeeLevel(FeeLevel.CENTERLEVEL));
}
} catch (PropertyNotFoundException pnfe) {
throw new FeeException(pnfe);
}
}
private FeeLevelEntity createFeeLevel(final FeeLevel feeLevel) {
return new FeeLevelEntity(this, feeLevel);
}
private void addFeeLevel(final FeeLevelEntity feeLevel) {
feeLevels.add(feeLevel);
}
private void validateFeeName(final String feeName) throws FeeException {
if (StringUtils.isBlank(feeName)) {
throw new FeeException(FeeConstants.INVALID_FEE_NAME);
}
}
private void validateGLCode(final GLCodeEntity glCode) throws FeeException {
if (glCode == null) {
throw new FeeException(FeeConstants.INVALID_GLCODE);
}
}
private void validateFeeCateogry(final CategoryTypeEntity categoryType) throws FeeException {
if (categoryType == null) {
throw new FeeException(FeeConstants.INVALID_FEE_CATEGORY);
}
}
private FeeStatusEntity retrieveFeeStatusEntity(final FeeStatus status) throws FeeException {
try {
return ApplicationContextProvider.getBean(LegacyMasterDao.class).findMasterDataEntityWithLocale(FeeStatusEntity.class, status.getValue());
} catch (PersistenceException pe) {
throw new FeeException(pe);
}
}
public boolean isMonthly() {
return this.feeFrequency.getFeeMeetingFrequency().isMonthly();
}
public boolean isWeekly() {
return this.feeFrequency.getFeeMeetingFrequency().isWeekly();
}
public FeeDto toDto() {
FeeDto feeDto = new FeeDto();
feeDto.setId(Short.toString(this.feeId));
feeDto.setName(this.feeName);
feeDto.setCategoryType(this.categoryType.getName());
feeDto.setFeeStatus(this.feeStatus.toDto());
feeDto.setActive(isActive());
feeDto.setCustomerDefaultFee(this.isCustomerDefaultFee());
feeDto.setRateBasedFee(this instanceof RateFeeBO);
feeDto.setChangeType(this.changeType);
FeeFrequencyDto feeFrequencyDto = this.feeFrequency.toDto();
feeDto.setFeeFrequency(feeFrequencyDto);
GLCodeDto glCodeDto = this.glCode.toDto();
feeDto.setGlCodeDto(glCodeDto);
feeDto.setGlCode(glCodeDto.getGlcode());
feeDto.setOneTime(isOneTime());
feeDto.setPeriodic(isPeriodic());
feeDto.setTimeOfDisbursement(isTimeOfDisbursement());
if (this instanceof AmountFeeBO) {
Money amount = ((AmountFeeBO) this).getFeeAmount();
feeDto.setCurrencyId(amount.getCurrency().getCurrencyId().intValue());
feeDto.setAmount(amount.toString(AccountingRules.getDigitsAfterDecimal()));
feeDto.setRateBasedFee(false);
} else {
RateFeeBO rateFeeBo = (RateFeeBO) this;
feeDto.setRate(rateFeeBo.getRate());
feeDto.setFeeFormula(rateFeeBo.getFeeFormula().toDto());
feeDto.setRateBasedFee(true);
}
return feeDto;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FeeBO)) {
return false;
}
FeeBO rhs = (FeeBO) obj;
return new EqualsBuilder().append(this.getFeeId(), rhs.getFeeId()).append(this.getFeeName(), rhs.getFeeName()).isEquals();
}
@Override
public int hashCode() {
int initialNonZeroOddNumber = 7;
int multiplierNonZeroOddNumber = 7;
return new HashCodeBuilder(initialNonZeroOddNumber, multiplierNonZeroOddNumber).append(this.getFeeId()).append(this.getFeeName()).toHashCode();
}
@Override
public String toString() {
return new ToStringBuilder(this).toString();
}
}